JS Sort is "weird".
Tags: javascript, sort
Published: 2021-03-24 05:51:54

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":

```js
[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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 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:

```js
[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](https://stackoverflow.com/questions/234683/javascript-array-sort-implementation) about how sort is implemented inside the JS motor, isn't hard to see that it's a simple [std::qsort](https://en.cppreference.com/w/cpp/algorithm/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:

```js
[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:
```js
// 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:
```js
// 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:

```js
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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) which is pretty good for sorting some words or letters considering that it can differentiate between upper or lower case and also accents.