Sorting out Javascript Sort
Tags: beginners, javascript, tutorial
Published: 2019-07-03 19:26:06

##You Don't Know Sort
Until recently, I'd really underestimated how much Javascript's [sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) method can accomplish. 

It's a tool that can easily be taken for granted, especially in those circumstances when you can just call it on an array and, without any extra effort at all, watch it magically rearrange its elements in order as you expect it to. 

```javascript
// Orders names alphabetically 
let myPets = ["Oreo", "Shortbread", "Peanut", "Souffie", "Tella"];
pets.sort();
// => [ 'Oreo', 'Peanut', 'Shortbread', 'Souffie', 'Tella' ]

// Order numbers numerically
let numbers = [3, 2, 1, 6, 5, 4];
numbers.sort();
// => [ 1, 2, 3, 4, 5, 6 ]


// Don't stop reading here! You know nothing yet!
```

However, `sort` on its own doesn't exactly behave as one would expect it to. For example, when called on `amazingInventions`, an array of capitalized and lowercased words, `sort` will order all the capitalized words before the lowercased ones. It's a bit odd and a little inconvenient, but I can see that there's *some* logic involved, so I'm not ready to riot over it yet. 

```javascript
let amazingInventions = ['computer', 'Internet', 'telegraph', 'vaccines'];
amazingInventions.sort();
// returns . . . . . . . [ 'Internet', 'computer', 'telegraph', 'vaccines' ]
// when you expected . . [ 'computer', 'Internet', 'telegraph', 'vaccines' ]
```
The function also appears to order numbers until you introduce multi-digit and negative numbers into your arrays, and that's when you really start to notice that something's not quite right. 
 
```javascript
let complexNumbers = [1, 3, -2, -1, 5, 11];
complexNumbers.sort();
// returns . . . . . . . [ -1, -2, 1, 11, 3, 5 ]
// when you expected . . [ -2, -1, 1, 3, 5, 11 ] 
```

In the example above, `sort` places `-1` before `-2`, and inserts `11` between `1` and `3`. That order clearly makes no sense, so how does this happen? 

##How Sort Works
It turns out that Javascript's `sort` sorts numbers just like a dictionary sorts words. Remember when you were a kid and learned how to alphabetize words letter by letter from left to right? `sort` is doing the same thing here. And regardless of whether your input is an array of strings or numbers or both, it'll interpret each element like a string and will systematically order elements one character unit at a time according to its [Unicode code point](https://www.unicode.org/Public/UCD/latest/charts/CodeCharts.pdf). 

Let's take a look into this for ourselves. Below, we have an array containing a variety of characters. It includes one lowercase and one uppercase letter, single and double-digit numbers, and we'll also toss in a dollar sign for good measure. 

```javascript
let randomStuff = ["a", "A", "1", "2" "12", "$"];
```

To make the point clearer, I'll use [`charCodeAt()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt) and create a handy reference that points each element to its *first* character's character code. Don't worry about the process, but just pay attention to the return.

```javascript
charCodes = {}
for(let el of randomStuff) {
charCodes[el] = el.charCodeAt(0)
}
// => { 
// '1':  49, 
// '2':  50, 
// '12': 49, 
// '$':  36, 
//  A:   65, 
//  a:   97 } 
```

You'll notice that `1` and `12` share the same first character, **1**, therefore each also shares the same *first* character code, **49**. So by this logic of comparing first characters only, `12` would be ordered before `2` because `sort` is using `12`'s first digit's character code to compare it with `2`'s. 

Let's sort the array using only `.sort()`, and we'll get this return.

```javascript
arr.sort();
// => [ '$', '1', '12', '2', 'A', 'a' ]
```

So, understanding that `sort` looks at elements character by character and compares by character code, it makes sense that capital `A` would come before lowercase `a` and that `$` would be first in line before everything else. `sort` is still in a way rearranging elements in numerical order, but strictly by each character's character code. Yes, the result still looks wonky, but at least we understand now it's not completely random and that it does follow a predictable set of rules. 


##Let's Sort Stuff!
Now that we've made more sense of `.sort()`, we can really use it to its fullest potential by taking advantage of the fact that it is a [higher-order function](https://eloquentjavascript.net/05_higher_order.html). I'll try not to sound super repetitive while explaining this but, a higher-order function is a type of function that can take another function as an argument or has a return value that is itself a function. Some examples of other common higher-order functions we use are `forEach`, `map`, `filter`, and `reduce`.

In the context of `sort`, we want to pass in a "compare function", and the best part about being able to do that is that we can really make `sort` do precisely what we want, whether that be sorting array elements purely alphabetically, numerically, or by properties. We can do quite a lot!

###Sort Purely Alphabetically
I was an English teacher in a past life, so it really bugs me to see words "alphabetized" by uppercase then lowercase letters. It's not how you'd see it in a dictionary, so it's no reason to let `sort` get away with this kind of behavior. 

To fix the faulty alphabetical ordering of `sort`, our compare function will do the following:
- Compare words, two at a time
- Lowercase words before comparisons to prevent division between lower and uppercased words! (*Note:* this won't affect the element in the end, but it sets pairs up nicely for a fair comparison)
- Apply the following logic: If word `a`'s character code is lower than word `b`'s, return `-1`, else return `1`

The logic we apply is important here because the return value determines how we'll sort each element. A negative return means that `a` should be sorted before `b` and a positive return means that `b` should be sorted before `a`. 

```javascript
let pWords = ["Paris", "panic", "potato", "Portugal"]

pWords.sort() // applying only .sort()
// => [ 'Paris', 'Portugal', 'panic', 'potato' ] -- BAD.

// create compare function
function compareWords(a,b) {
  if (a.toLowerCase() < b.toLowerCase()) {
    return -1; 
  } else { 
    return 1;
  }
}

// pass compareWords function into .sort()
pWords.sort(compareWords)
// => [ 'panic', 'Paris', 'Portugal', 'potato' ] -- MUCH BETTER.
```

That's exactly how I want it sorted and I feel so much better. And just because I prefer my code to look succinct, I might slim it down using an arrow function and a ternary operator.

```javascript
pWords.sort((a,b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
```

Nice!

To sort in reverse-alphabetical order, just reverse the comparison operator.
```javascript
pWords.sort((a,b) => a.toLowerCase() > b.toLowerCase() ? -1 : 1)
```


###Numeric Order
A compare function that orders numeric arrays uses the same concept as `compareWords`. Again, we'll compare two elements `a` and `b`, but this time using the subtraction operator. 

Similarly, if the difference returns a negative value, `a` is sorted before `b`, if the difference returns a positive value, `b` is sorted before `a`. 

```javascript
let numbers = [1, 101, 23, 18]

// You could do it this way
function compareNumbers(a,b) {
  return a - b;
}
numbers.sort(compareNumbers);


// But this is much simpler
numbers.sort((a,b) => a - b);
// => [ 1, 18, 23, 101 ]
```


###Order by Word Length
We can get a little more creative here and instead of ordering alphabetically, we can order by word length. Remember how we sorted numbers? It's a lot like that. We're not comparing letters anymore, but we're comparing the *number* of characters in a word, which is why the order of "Paris" and "panic" don't matter.

```javascript
pWords.sort((a,b) => a.length - b.length)
// => [ 'Paris', 'panic', 'potato', 'Portugal' ]
```


###Ordering objects by property
This is where `sort` gets really fun. Imagine we've got an array of objects. I created an array containing a small sampling of [McDonald's burgers](https://www.mcdonalds.com/us/en-us/about-our-food/nutrition-calculator.html). Included in each object is the name of the burger, the calorie count, and a general list of ingredients that make the burger.

I can sort this array of burgers in a number of different ways, each by a different property. First, I'll sort alphabetically by burger name.

To do this, we'll follow the structure of our alphabetical or numerical compare functions, but this time, we'll chain a property name of our objects to our variables `a` and `b`. 

```javascript
let McDBurgers = [
  {name: "hamburger",
   calories: 250,
   ingredients: ["bun", "beef patty", "ketchup", "pickle slices", "onions", "mustard"]},
  {name: "Cheeseburger",
   calories: 300,
   ingredients: ["bun", "beef patty", "american cheese", "ketchup", "pickle slices", "onions", "mustard"]},
  {name: "McChicken",
   calories: 410,
   ingredients: ["bun", "chicken patty", "lettuce", "mayonnaise"]},
  {name: "Filet-O-Fish",
   calories: 390,
   ingredients: ["bun", "fish filet patty", "american cheese", "tartar sauce"]}  
 ];

// Sort by burger name
McDBurgers.sort((a,b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1)

//=> [
//  {
//    name: 'Cheeseburger',
//    calories: 300,
//    ...
//  },
//  {
//    name: 'Filet-O-Fish',
//    calories: 390,
//    ...
//  },
//  {
//    name: 'hamburger',
//    calories: 250,
//    ...
//  },
//  {
//    name: 'McChicken',
//    calories: 410,
//    ...
//  }
//]
```

Look! Our burger objects are neatly ordered alphabetically!

I can go even further and order them by calorie count or by count of unique ingredients.

```javascript
// sort by calorie count
McDBurgers.sort((a,b) => a.calories - b.calories)

// sort by number of unique ingredients
McDBurgers.sort((a,b) => a.ingredients.length - b.ingredients.length)
```

If we were to run these lines, we should be able to see each of McDBurger's burger objects rearrange accordingly. Plug each line of code into your console and see for yourself what returns come back! Then try to explore what other ways you can manipulate `sort` to order your arrays!