Why the default sort isn't enough
Calling sort() with nothing inside works for text, which it puts from A to Z. For numbers it does something surprising: it turns them into text first and compares the text.
console.log([10, 9, 1].sort());
console.log([10, 9, 1].sort((a, b) => a - b));
[ 1, 10, 9 ]
[ 1, 9, 10 ]
The first line is wrong for numbers, because the text "10" comes before "9". The second line fixes it with a compare function, and the same tool lets you sort in any order you like: the highest score first, the shortest word first, and so on.
How a compare function works
A compare function is a small function you give to sort. (a, b) => a - b is written in one line and has no name. It gets two items, a and b, and returns a number. sort calls it on two items at a time, and the number says which one goes first.
- A negative number puts
afirst. - A positive number puts
bfirst. - Zero means they are equal.
Rules you will use often:
- Low to high:
(a, b) => a - b - High to low:
(a, b) => b - a - Shortest first:
(a, b) => a.length - b.length
For high to low, think of two prices where b is bigger. Then b - a is positive, and a positive result puts b first.
Types in the compare function
TypeScript reads the type of a and b from the array: for a number[] they are numbers, and for a string[] they are strings. So the compare function needs no annotations. The types also catch slips. Subtracting two strings in a compare function for a string[] gets this error from the compiler:
error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
error TS2363: The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
The compiler still writes the JavaScript, though, and that JavaScript runs. Subtracting strings gives NaN, so the order does not change. Fix the error, don't ignore it.
Two rules
When one rule isn't enough, the function needs a body in braces. Check the first rule. Use the second one only on a tie.
let nums: number[] = [25, 13, 5, 41, 33];
nums.sort((a, b) => {
if (a % 10 !== b % 10) {
return a % 10 - b % 10;
}
return a - b;
});
console.log(nums);
[ 41, 13, 33, 5, 25 ]
The first rule compares the last digits, a % 10 and b % 10. Only when they are the same does the function reach return a - b, which puts the smaller number first.
Comparing text with localeCompare
You can't subtract strings. The method localeCompare does the job: x.localeCompare(y) gives a negative number when x comes before y from A to Z, a positive number when it comes after, and 0 when they are equal. That is exactly what a compare function must return.
sort changes the array itself
The sort method rearranges the array it is called on, and it returns that same array. To keep the original order, sort a copy made with slice():
let words: string[] = ["pear", "fig", "banana", "kiwi"];
console.log(words.slice().sort((a, b) => a.length - b.length));
console.log(words);
[ 'fig', 'pear', 'kiwi', 'banana' ]
[ 'pear', 'fig', 'banana', 'kiwi' ]
Items that compare as equal keep their original order, because sort is stable.
The lesson's task
A pet shop has prices and names. Sort prices from high to low, and sort names by their last letter, with names that share a last letter going from A to Z. The loops that print both are already in the starter code.
The prices need one rule, b - a. The names need two. Inside the function, take the last letter of each name: it sits at index length - 1. If the two last letters differ, return their localeCompare. Otherwise return a.localeCompare(b), which puts Max before Rex.
let prices: number[] = [12, 5, 30, 8];
let names: string[] = ["Rex", "Luna", "Bella", "Max", "Bo", "Coco"];
prices.sort((a, b) => b - a);
for (let price of prices) {
console.log(price);
}
names.sort((a, b) => {
let lastA = a[a.length - 1];
let lastB = b[b.length - 1];
if (lastA !== lastB) {
return lastA.localeCompare(lastB);
}
return a.localeCompare(b);
});
for (let name of names) {
console.log(name);
}
It prints:
30
12
8
5
Bella
Luna
Bo
Coco
Max
Rex
Common mistakes
- Sorting numbers without a compare function.
[10, 9, 1].sort()gives[1, 10, 9]. - Swapping
aandb.a - bsorts low to high,b - ahigh to low. - Dropping the second rule. With only the last-letter comparison, ties keep their original order and the names come out as
Luna, Bella, Bo, Coco, Rex, Max. Rex precedes Max only because it was first in the array. - Using
return a < b. A compare function must return a number, not a boolean.
Key takeaways
- Give
sorta compare function ofaandb; a negative result putsafirst. - Use
a - bfor low to high andb - afor high to low. - For two rules, return the first rule's result when it isn't a tie, and fall back to the second rule otherwise.
- Compare text with
localeCompare. sortchanges the array; copy it withslice()to keep the original.
