There have been other questions about How to compare arrays in JavaScript?. What I want to know is the most straightforward way to write/use a three-way comparison function like the one required by Array.sort(). Here's an example using the default one which doesn't work well:
> [ [4,5,10], [4,5,6], [4,1,2] ].sort() // no compare function, uses the default one
[ [ 4, 1, 2 ],
[ 4, 5, 10 ], // oops, string sorting makes 10 < 6
[ 4, 5, 6 ] ]
This is what I came up with:
// return -1 if lhs is "less" than rhs, +1 if "greater", and 0 if equal
// if lhs and rhs have different lengths, only the shorter part will be considered
function compareArrays(lhs, rhs) {
for (var ii = 0; ii < lhs.length; ii++) {
if (lhs[ii] < rhs[ii]) {
return -1;
} else if (lhs[ii] > rhs[ii]) {
return 1;
}
}
return 0;
}
Which gives us what we want:
> [ [4,5,10], [4,5,6], [4,1,2] ].sort(compareArrays)
[ [ 4, 1, 2 ],
[ 4, 5, 6 ],
[ 4, 5, 10 ] ]
Is there something more like a one-liner, or must I define my own function whenever I want to do this?
Supporting old browsers is not a requirement. Using libraries like jQuery or Underscore is OK.
One way to look at this is "The first nonzero value from a standard three-way compare applied to each pair of elements." But even then I haven't found a good fit in existing libraries.
I'd go with a generic comparison-maker function to be used in a functional way:
function compareArrays(compareItems) {
return function(a, b) {
for (var r, i=0, l=Math.min(a.length, b.length); i<l; i++)
if (0 != (r = compareItems(a[i], b[i])))
return r;
return a.length - b.length;
};
}
// Examples:
var compareNumberArrays = compareArray(function(a,b){ return a-b; }),
compareGenericArrays = compareArray(function(a,b){ return +(a>b)||-(b>a); });
Now you can use
[ [4,5,10], [4,5,6], [4,1,2], [4,5] ].sort(compareNumberArrays)
Is there something more like a one-liner, or must I define my own function whenever I want to do this?
Comparing arrays is too complex for a one-liner, you should use a helper function. There's no built-in one that you can use everywhere.
Probably not the shortest possible, but the fewest lines I can get to sensibly is:
function compareArrays(lhs, rhs) {
var result;
lhs.some(function(v, i) {
return (result = v - rhs[i]);
});
return result;
}
or less sensibly:
function compareArrays(lhs, rhs, r) {
lhs.some(function(v, i) {return (r = v - rhs[i])});
return r;
}
Edit
Seems you don't want numbers. The compare part can be any relationship you want, e.g. for strings:
function compareArrays(lhs, rhs, r) {
lhs.some(function(v, i) {return (r = v < rhs[i]? -1 : v > rhs[i]? 1 : 0)});
return r;
}
[['a','b','c'],['a','c','d'],['a','b','d']].sort(compareArrays) // a,b,c,a,b,d,a,c,d
But the compare function needs to know what it's getting so it doesn't sort numbers as strings or strings as numbers (and so on…).
If you know that they are always triplets of numbers (arrays of length 3), you could do this:
function combineNum(array) {
return (array[0] * 100) + (array[1] * 10) + array[2];
}
function compareArrays(lhs, rhs) {
return combineNum(rhs) - combineNum(lhs);
}