It does not take a highly experienced JS programmer to notice that Array.prototype.sort
works in his own "weird" way. But maybe you're not familiar with JS at all, in that case, let me show you what i mean by "weird":
[32, 2, 43, 101, 1025, 5].sort()
// Result: (5) [101, 1025, 2, 32, 43, 5]
TL:DR
The sort method turns each and every value into string and then compare their sequences of UTF-16 code units values, leading to the "weird" behavior.
Not so brief explanation:
Going into MDN docs we are given the following information:
The
sort()
method sorts the elements of an array in place and returns the sorted array. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.
Now that we know that the value is really compared as a string in UTF-16, lets check the values for our first test in this format:
[32, 2, 43, 101, 1025, 5].map(
value => ({
value,
charCodeAt0: value.toString().charCodeAt(0)
})
)
/* Result: [{…}, {…}, {…}, {…}, {…}, {…}]
0: {value: 32, unityCharCode: 51}
1: {value: 2, unityCharCode: 50}
2: {value: 43, unityCharCode: 52}
3: {value: 101, unityCharCode: 49}
4: {value: 1025, unityCharCode: 49}
5: {value: 5, unityCharCode: 53}
*/
That's nice, if you check some stackoverflow questions about how sort is implemented inside the JS motor, isn't hard to see that it's a simple std::qsort from C++ which will sort the given values alphabetically ascending if no comparison function
is provided.
So if we provide a function which compare the charCodeAt0
property for our generated object, we should end with a list sorted the same way, right? Let's test it:
[32, 2, 43, 101, 1025, 5].map(value => ({
value,
unityCharCode: value.toString().charCodeAt(0)
})
).sort(
(a, z) => a.unityCharCode - z.unityCharCode
)
/* Result: [{…}, {…}, {…}, {…}, {…}, {…}]
0: {value: 101, unityCharCode: 49}
1: {value: 1025, unityCharCode: 49}
2: {value: 2, unityCharCode: 50}
3: {value: 32, unityCharCode: 51}
4: {value: 43, unityCharCode: 52}
5: {value: 5, unityCharCode: 53}
*/
Yep, is seems just like the first test.
But, which function should i use?
Having a little more understanding of how the Array.prototype.sort
runs, we can pass a comparison function
to handle the sort in the way we want it to:
Alphabetically ascending:
// Only Numbers:
[32, 2, 43, 101, 1025, 5].sort((a, z) => a - z)
// Result: [2, 5, 32, 43, 101, 1025]
// Only Letters:
["j", "A", "c", "D", "a", "d", "e", "k"].sort(
(a,z) => a > z ? 1 : -1
)
// Result: ["A", "D", "a", "c", "d", "e", "j", "k"]
// Letters and Numbers:
[32, 43, 'j', 'A', 1025, 5, 'a', 'c', 'b']
.sort()
.sort((a,z) => a > z ? 1 : -1)
// Result: ["A", "a", "b", "c", "j", 5, 32, 43, 1025]
Alphabetically descending:
// Only Numbers:
[32, 2, 43, 101, 1025, 5].sort((a, z) => z - a)
// Result: [1025, 101, 43, 32, 5, 2]
// Only Letters:
["j", "A", "c", "D", "a", "d", "e", "k"].sort(
(a,z) => a < z ? 1 : -1
)
// Result: ["k", "j", "e", "d", "c", "a", "D", "A"]
// Letters and Numbers:
[32, 43, 'j', 'A', 1025, 5, 'a', 'c', 'b']
.sort()
.sort((a,z) => a < z ? 1 : -1)
// Result: ["j", "c", "b", "a", "A", 1025, 43, 32, 5]
In case you want to take it a step further and use a custom function that could validate any of the above cases for you, there you go:
const isNumber = (v) => !isNaN(v)
const compareNumbers = (a, z, order = 'asc') => ({
asc: a - z,
desc: z - a
}[order]);
const compareWords = (a, z, order = 'asc') => ({
asc: a > z ? 1 : -1,
desc: a < z ? 1 : -1
}[order]);
const compareFunction = (a, z, order = 'asc') => {
if(isNumber(a) && !isNumber(z)) return 1;
if(!isNumber(a) && isNumber(z)) return -1;
if(isNumber(a) && isNumber(z)) {
return compareNumbers(a, z, order)
}
return compareWords(a, z, order)
}
[32, 43, 'j', 'A', 1025, 5, 'a', 'c', 'b'].sort(
(a, z) => compareFunction(a, z)
)
//Result: ["A", "a", "b", "c", "j", 5, 32, 43, 1025]
What about undefined?
Well, undefined
is a edge case for sort, it will always have a bigger comparison value than any other, so it will always shown in the end of a sorted list.
It doesn't get more complex than this (no pun intended).
You can also check the String.prototype.localeCompare which is pretty good for sorting some words or letters considering that it can differentiate between upper or lower case and also accents.
Top comments (0)