I desperately need to implement client side sorting that emulates sorting through our tastypie api, which can take multiple fields and return sorted data. So if for example I have data like:
arr = [
{ name: 'Foo LLC', budget: 3500, number_of_reqs: 1040 },
{ name: '22nd Amendment', budget: 1500, number_of_reqs: 2000 },
{ name: 'STS 10', budget: 50000, number_of_reqs: 500 },
...
etc.
]
and given columns to sort e.g.:['name', '-number_of_reqs']
it should sort by name
(ascending) and number_of_reqs
(descending). I can't get my head around this,
first of all it has to be "natural sort", it supposed to be fairly easy to get if we're talking about sorting a single column, but I need to be able to sort in multiple.
Also I'm not sure why I'm getting different results (from the way how api does it) when using lodash's _.sortBy
? Is _.sortBy
not "natural" or it's our api broken?
Also I was looking for an elegant solution. Just recently started using Ramdajs, it's so freaking awesome. I bet it would be easier to build sorting I need using that? I've tried, still can't get it right. Little help?
upd:
I found this and using it with Ramda like this:
fn = Rpose(R.sort(naturalSort), R.pluck("name"))
fn(arr)
seems to work for flat array, yet I still need to find a way to apply it for multiple fields in my array
I desperately need to implement client side sorting that emulates sorting through our tastypie api, which can take multiple fields and return sorted data. So if for example I have data like:
arr = [
{ name: 'Foo LLC', budget: 3500, number_of_reqs: 1040 },
{ name: '22nd Amendment', budget: 1500, number_of_reqs: 2000 },
{ name: 'STS 10', budget: 50000, number_of_reqs: 500 },
...
etc.
]
and given columns to sort e.g.:['name', '-number_of_reqs']
it should sort by name
(ascending) and number_of_reqs
(descending). I can't get my head around this,
first of all it has to be "natural sort", it supposed to be fairly easy to get if we're talking about sorting a single column, but I need to be able to sort in multiple.
Also I'm not sure why I'm getting different results (from the way how api does it) when using lodash's _.sortBy
? Is _.sortBy
not "natural" or it's our api broken?
Also I was looking for an elegant solution. Just recently started using Ramdajs, it's so freaking awesome. I bet it would be easier to build sorting I need using that? I've tried, still can't get it right. Little help?
upd:
I found this and using it with Ramda like this:
fn = R.pose(R.sort(naturalSort), R.pluck("name"))
fn(arr)
seems to work for flat array, yet I still need to find a way to apply it for multiple fields in my array
Share edited Oct 13, 2014 at 0:19 iLemming asked Oct 12, 2014 at 8:37 iLemmingiLemming 36.3k61 gold badges198 silver badges316 bronze badges5 Answers
Reset to default 8fn = R.pose(R.sort(naturalSort), R.pluck("name"))
seems to be working
Really? I would expect that to return a sorted array of names, not sort an array of objects by their name property.
Using sortBy
unfortunately doesn't let us supply a custom parison function (required for natural sort), and bining multiple columns in a single value that pares consistently might be possible but is cumbersome.
I still don't know how to do it for multiple fields
Functional programming can do a lot here, unfortunately Ramda isn't really equipped with useful functions for parators (except R.parator
). We need three additional helpers:
on
(like the one from Haskell), which takes ana -> b
transformation and ab -> b -> Number
parator function to yield a parator on twoa
s. We can create it with Ramda like this:var on = R.curry(function(map, cmp) { return R.useWith(cmp, map, map); return R.useWith(cmp, [map, map]); // since Ramda >0.18 });
or
- just like||
, but on numbers not limited to booleans likeR.or
. This can be used to chain two parators together, with the second only being invoked if the first yields0
(equality). Alternatively, a library likethenBy
could be used for this. But let's define it ourselves:var or = R.curry(function(fst, snd, a, b) { return fst(a, b) || snd(a, b); });
negate
- a function that inverses a parison:function negate(cmp) { return R.pose(R.multiply(-1), cmp); }
Now, equipped with these we only need our parison functions (that natural sort is an adapted version of the one you found, see also Sort Array Elements (string with numbers), natural sort for more):
var NUMBER_GROUPS = /(-?\d*\.?\d+)/g;
function naturalCompare(a, b) {
var aa = String(a).split(NUMBER_GROUPS),
bb = String(b).split(NUMBER_GROUPS),
min = Math.min(aa.length, bb.length);
for (var i = 0; i < min; i++) {
var x = aa[i].toLowerCase(),
y = bb[i].toLowerCase();
if (x < y) return -1;
if (x > y) return 1;
i++;
if (i >= min) break;
var z = parseFloat(aa[i]) - parseFloat(bb[i]);
if (z != 0) return z;
}
return aa.length - bb.length;
}
function stringCompare(a, b) {
a = String(a); b = String(b);
return +(a>b)||-(a<b);
}
function numberCompare(a, b) {
return a-b;
}
And now we can pose exactly the parison on objects that you want:
fn = R.sort(or(on(R.prop("name"), naturalCompare),
on(R.prop("number_of_reqs"), negate(numberCompare))));
fn(arr)
I think this works.
var arr = [
{ name: 'Foo LLC', budget: 3500, number_of_reqs: 1040 },
{ name: '22nd Amendment', budget: 1500, number_of_reqs: 2000 },
{ name: 'STS 10', budget: 50000, number_of_reqs: 5000 },
{ name: 'STS 10', budget: 50000, number_of_reqs: 500 }
];
var columns = ['name', 'number_of_reqs'];
var NUMBER_GROUPS = /(-?\d*\.?\d+)/g;
var naturalSort = function (a, b, columnname) {
var a_field1 = a[columnname],
b_field1 = b[columnname],
aa = String(a_field1).split(NUMBER_GROUPS),
bb = String(b_field1).split(NUMBER_GROUPS),
min = Math.min(aa.length, bb.length);
for (var i = 0; i < min; i++) {
var x = parseFloat(aa[i]) || aa[i].toLowerCase(),
y = parseFloat(bb[i]) || bb[i].toLowerCase();
if (x < y) return -1;
else if (x > y) return 1;
}
return 0;
};
arr.sort(function(a, b) {
var result;
for (var i = 0; i < columns.length; i++) {
result = naturalSort(a, b, columns[i]);
if (result !== 0) return result; // or -result for decending
}
return 0; //If both are exactly the same
});
console.log(arr);
Bergi's answer is useful and quite interesting, but it changes the API you requested. Here's one that creates the API you were seeking:
var multisort = (function() {
var propLt = R.curry(function(name, a, b) {
return a[name] < b[name];
});
return function(keys, objs) {
if (arguments.length === 0) {throw new TypeError('cannot sort on nothing');}
var fns = R.map(function(key) {
return key.charAt(0) === "-" ?
R.pipe(R.parator(propLt(R.substringFrom(1, key))), R.multiply(-1)) :
R.parator(propLt(key));
}, keys);
var sorter = function(a, b) {
return R.reduce(function(acc, fn) {return acc || fn(a, b);}, 0, fns);
}
return arguments.length === 1 ? R.sort(sorter) : R.sort(sorter, objs);
};
}());
multisort(['name', '-number_of_reqs'], arr); //=> sorted clone
It's manually curried rather than calling R.curry
because a fair bit of the work is involved in creating the separate sort functions, which could then be reused if you are sorting many lists with the same set of keys. If that's not a concern, this could be simplified a bit.
If you're willing to add another dependency to your project, @panosoft/ramda-utils es with a pareProps function that does exactly what the original question was asking for.
So, given your original example, to sort descending by budget and then by name, you could do something like this:
var props = ["-budget", "name"];
var parator = Ru.pareProps(props);
var sortedList = R.sort(parator, arr);
use the javascript native sort:
Array.prototype.multisort = function(columns) {
var arr = this;
arr.sort(function(a, b) {
return pare(a, b, 0);
});
function pare(a, b, colindex) {
if (colindex >= columns.length) return 0;
var columnname = columns[colindex];
var a_field1 = a[columnname];
var b_field1 = b[columnname];
var asc = (colindex % 2 === 0);
if (a_field1 < b_field1) return asc ? -1 : 1;
else if (a_field1 > b_field1) return asc ? 1 : -1;
else return pare(a, b, colindex + 1);
}
}
var arr = [{ name: 'Foo LLC', budget: 3500, number_of_reqs: 1040 },
{ name: '22nd Amendment',budget: 1500, number_of_reqs: 2000 },
{ name: 'STS 10', budget: 50000,number_of_reqs: 5000 },
{ name: 'STS 10', budget: 50000,number_of_reqs: 500 }];
arr.multisort(['name', 'number_of_reqs']);
if (window.console) window.console.log(arr);
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1741589345a4355223.html
评论列表(0条)