I need to perform a sort on an array and if two elements are equal I then need to perform a secondary sort on a different key within those elements. Having a look at the Mozilla Developer Network docs for array.sort there is a nice snippet at code at the bottom to handle the first sort. I liked it because it was succinct and shows how you can write powerful JS.
Here is what I tried based on the code from MDN. This correctly does the first sort.
// the array to be sorted
var list = [{name:'Delta', ref: 456}, {name:'Delta', ref: 123}, {name:'alpha', ref: 789}, {name:'CHARLIE', ref: 012}, {name:'bravo', ref: 345}];
// temporary array holds objects with position and sort-value
var mapped = list.map(function (el, i) {
return {
index: i,
value: el.name.toLowerCase(),
secondaryValue: el.ref
};
});
// sorting the mapped array containing the reduced values
mapped.sort(function (a, b) {
return +(a.value > b.value) || +(a.value === b.value) - 1;
});
// container for the resulting order
var result = mapped.map(function (el) {
return list[el.index];
});
console.log(list);
console.log(result);
Now I know I can modify the comparison function like this to complete the requirement of the secondary sort:
// sorting the mapped array containing the reduced values
mapped.sort(function (a, b) {
if (a.value === b.value){
return +(a.secondaryValue > b.secondaryValue) || +(a.secondaryValue === b.secondaryValue) - 1;
}
return +(a.value > b.value) || - 1;
});
But, to me, this loses some of the charm of return +(a.value > b.value) || +(a.value === b.value) - 1;
- which is pretty cool in my opinion.
Question: Is there a more elegant way to perform the secondary sort?
Restriction: Pure JS only. ES5 compatible preferably but interested to hear if ES6 can help.
Use the syntax array[:, j – 1] to extract the j -th column of an array. Call numpy. argsort(a) to return the sorted indices of the column a . Then use these sorted indices to sort the rows of the same array by column a .
The sort() method allows you to sort elements of an array in place. Besides returning the sorted array, the sort() method changes the positions of the elements in the original array. By default, the sort() method sorts the array elements in ascending order with the smallest value first and largest value last.
You can chain the sort comparison with a logic or ||
, because same values are evaluated to 0
and that is falsy so the next part is evaluated.
mapped.sort(function (a, b) {
return (+(a.value > b.value) || +(a.value === b.value) - 1) ||
(+(a.secondaryValue > b.secondaryValue) || +(a.secondaryValue === b.secondaryValue) - 1);
});
Or use more compact version of the above comparison
var list = [{name:'Delta', ref: 456}, {name:'Delta', ref: 123}, {name:'alpha', ref: 789}, {name:'CHARLIE', ref: 12}, {name:'bravo', ref: 345}],
mapped = list.map(function (el, i) {
return {
index: i,
value: el.name.toLowerCase(),
secondaryValue: el.ref
};
});
mapped.sort(function (a, b) {
return a.value.localeCompare(b.value) || a.secondaryValue - b.secondaryValue;
});
var result = mapped.map(function (el) {
return list[el.index];
});
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
I'm on the fence about answering this so I'm going to answer the question of:
How can an array be concisely sorted by multiple sorting functions?
The question isn't exactly what you're asking, but this way it's a little better defined and answerable.
Sorting an array with a single function is pretty straightforward using Array.prototype.sort
, but unfortunately wasn't forward-thinking enough to allow for multiple parameters. It also has the problem of modifying the array directly rather than being chainable:
a = [2, 1, 3];
a.sort(function (a, b) {
return b - a;
});
Sorting by multiple dimensions is trickier because all dimensions need to be accounted for in the sort
callback, or you need to write your own sorting algorithm, which is irksome.
If you're using underscore or lodash, there are various sorting utilities available which are convenient, but if you're not using another library a multisort
function isn't overly difficult, you can feel free to use the one i whipped up quickly which I haven't hardly tested:
multisort = (function () {
// or for commonjs
// module.exports = (function () {
function slice(arr) {
return Array.prototype.slice.call(arr);
}
// multisort(arr, [fn1], [fn2] ...)
return function multisort(arr) {
var args;
//copy the arguments into a new array
args = slice(arguments);
//copy the array to a new array and sort the new array
return slice(arr).sort(function (a, b) {
var i,
fn,
res;
//iterate over the sort callbacks
for (i = 1; i < args.length; i++) {
fn = args[i];
//call the sort callback,
//this version doesn't do anything special with setting a context
res = fn(a, b);
//if the sort value is anything other than 0, return it
//otherwise fall to the next sort function
if (res) {
return res;
}
}
return 0;
});
}
}());
You'd use it as:
mapped = multisort(mapped, function (a, b) {
return +(a.value > b.value) || +(a.value === b.value) - 1;
}, function (a, b) {
return +(a.secondaryValue > b.secondaryValue) || +(a.secondaryValue === b.secondaryValue) - 1;
});
This format keeps the sorting functions distinct from one-another, so that they can be mixed and matched in whatever order you'd prefer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With