I don't think most of the answers really work, and certainly there is none that works and uses purely underscore at the same time.
This answer provides sorting for multiple columns, with the ability to reverse the sort order for some of them, all in one function.
It also builds on the final code step by step, so you may want to take the last code snippet:
I have used this for two columns only (first sort by a
, then by b
):
var array = [{a:1, b:1}, {a:1, b:0}, {a:2, b:2}, {a:1, b:3}];
_.chain(array)
.groupBy(function(i){ return i.a;})
.map(function(g){ return _.chain(g).sortBy(function(i){ return i.b;}).value(); })
.sortBy(function(i){ return i[0].a;})
.flatten()
.value();
Here is the result:
0: {a: 1, b: 0}
1: {a: 1, b: 1}
2: {a: 1, b: 3}
3: {a: 2, b: 2}
I am sure this can be generalized for more than two...
Another version that might be faster:
var array = [{a:1, b:1}, {a:1, b:0}, {a:2, b:2}, {a:1, b:3}];
_.chain(array)
.sortBy(function(i){ return i.a;})
.reduce(function(prev, i){
var ix = prev.length - 1;
if(!prev[ix] || prev[ix][0].a !== i.a) {
prev.push([]); ix++;
}
prev[ix].push(i);
return prev;
}, [])
.map(function(i){ return _.chain(i).sortBy(function(j){ return j.b; }).value();})
.flatten()
.value();
And a parametrized version of it:
var array = [{a:1, b:1}, {a:1, b:0}, {a:2, b:2}, {a:1, b:3}];
function multiColumnSort(array, columnNames) {
var col0 = columnNames[0],
col1 = columnNames[1];
return _.chain(array)
.sortBy(function(i){ return i[col0];})
.reduce(function(prev, i){
var ix = prev.length - 1;
if(!prev[ix] || prev[ix][0][col0] !== i[col0]) {
prev.push([]); ix++;
}
prev[ix].push(i);
return prev;
}, [])
.map(function(i){ return _.chain(i).sortBy(function(j){ return j[col1]; }).value();})
.flatten()
.value();
}
multiColumnSort(array, ['a', 'b']);
And a parametrized version for any number of columns (seems to work from a first test):
var array = [{a:1, b:1, c:9}, {a:1, b:1, c:3}, {a:2, b:2, c:10}, {a:1, b:3, c:0}];
function multiColumnSort(array, columnNames) {
if(!columnNames || !columnNames.length || array.length === 1) return array;
var col0 = columnNames[0];
if(columnNames.length == 1) return _.chain(array).sortBy(function(i){ return i[col0]; }).value();
return _.chain(array)
.sortBy(function(i){ return i[col0];})
.reduce(function(prev, i){
var ix = prev.length - 1;
if(!prev[ix] || prev[ix][0][col0] !== i[col0]) {
prev.push([]); ix++;
}
prev[ix].push(i);
return prev;
}, [])
.map(function(i){ return multiColumnSort(i, _.rest(columnNames, 1));})
.flatten()
.value();
}
multiColumnSort(array, ['a', 'b', 'c']);
If you want to be able to reverse the column sorting too:
var array = [{a:1, b:1, c:9}, {a:1, b:1, c:3}, {a:2, b:2, c:10}, {a:1, b:3, c:0}];
function multiColumnSort(array, columnNames) {
if(!columnNames || !columnNames.length || array.length === 1) return array;
var col = columnNames[0],
isString = !!col.toLocaleLowerCase,
colName = isString ? col : col.name,
reverse = isString ? false : col.reverse,
multiplyWith = reverse ? -1 : +1;
if(columnNames.length == 1) return _.chain(array).sortBy(function(i){ return multiplyWith * i[colName]; }).value();
return _.chain(array)
.sortBy(function(i){ return multiplyWith * i[colName];})
.reduce(function(prev, i){
var ix = prev.length - 1;
if(!prev[ix] || prev[ix][0][colName] !== i[colName]) {
prev.push([]); ix++;
}
prev[ix].push(i);
return prev;
}, [])
.map(function(i){ return multiColumnSort(i, _.rest(columnNames, 1));})
.flatten()
.value();
}
multiColumnSort(array, ['a', {name:'b', reverse:true}, 'c']);
To also support functions:
var array = [{a:1, b:1, c:9}, {a:1, b:1, c:3}, {a:2, b:2, c:10}, {a:1, b:3, c:0}];
function multiColumnSort(array, columnNames) {
if (!columnNames || !columnNames.length || array.length === 1) return array;
var col = columnNames[0],
isString = !!col.toLocaleLowerCase,
isFun = typeof (col) === 'function',
colName = isString ? col : col.name,
reverse = isString || isFun ? false : col.reverse,
multiplyWith = reverse ? -1 : +1,
sortFunc = isFun ? col : function (i) { return multiplyWith * i[colName]; };
if (columnNames.length == 1) return _.chain(array).sortBy(sortFunc).value();
return _.chain(array)
.sortBy(sortFunc)
.reduce(function (prev, i) {
var ix = prev.length - 1;
if (!prev[ix] || (isFun ? sortFunc(prev[ix][0]) !== sortFunc(i) : prev[ix][0][colName] !== i[colName])) {
prev.push([]); ix++;
}
prev[ix].push(i);
return prev;
}, [])
.map(function (i) { return multiColumnSort(i, _.rest(columnNames, 1)); })
.flatten()
.value();
}
multiColumnSort(array, ['a', {name:'b', reverse:true}, function(i){ return -i.c; }]);