Doesn’t Array.sort output as you expected? It’s because number is handled as string by default. If you want to sort the array on your needs, you need to write your own sorting logic.
Didn’t you get the following result when you tried to use Array.sort?
[27, 24, 100, 1, 2] -> 1,100,2,24,27
["27", "24", "100", "1", "002"] -> 002,1,100,24,27
["A", "AB", "ACC", "aaaa", "a", "ab", "acc"] -> A,AB,ACC,a,ab,acc
["27", "0xff", "100", "1", "002", "CCC", "AA", "aB"]
-> 002,0xff,1,100,27,AA,CCC,aB
Isn’t it what you want? Do you need to sort something like [3, 4, "A1", "B3", "AB5"]
? Then, this post is for you.
Sort number array
Let’s call sort method without parameter.
const nums = [27, 24, 100, 1, 2];
console.log(nums.sort().join(",")); // ASC: 1,100,2,24,27
The result might not be your desired output. If the compare function is not supplied to the sort method, it’s sorted in the following way.
If compareFn is not supplied, all non-undefined array elements are sorted by converting them to strings and comparing strings in UTF-16 code units order.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
The numbers are converted to string first. When it compares the first letters, 2 is bigger than 1. That’s why the result becomes like the above.
If you want to sort the array in numerical order, you have to specify the compare function.
console.log(nums.sort((a, b) => a - b).join(",")); // ASC: 1,2,24,27,100
console.log(nums.sort((a, b) => b - a).join(",")); // DESC: 100,27,24,2,1
console.log(nums.sort((a, b) => 0).join(",")); // Not sort: 100,27,24,2,1
Internal behavior is defined in the following way.
compareFn(a, b) return value | sort order |
---|---|
> 0 | sort a after b |
< 0 | sort a before b |
=== 0 | keep original order of a and b |
Let’s make it simple.
Formula | Result |
---|---|
a – b | ASC |
b – a | DESC |
Sort string array
Normal string sorting is easy.
function ascending(a: string, b: string) {
if (a > b) {
return 1;
}
if (a < b) {
return -1;
}
return 0;
}
function descending(a: string, b: string) {
if (a < b) {
return 1;
}
if (a > b) {
return -1;
}
return 0;
}
const strs = [
"AAA",
"A",
"AB",
"ACC",
"aaa",
"aaaa",
"a",
"ab",
"acc",
];
console.log(strs.sort().join(",")); // ASC: A,AAA,AB,ACC,a,aaa,aaaa,ab,acc
console.log(strs.sort(ascending).join(",")); // ASC: A,AAA,AB,ACC,a,aaa,aaaa,ab,acc
console.log(strs.sort(descending).join(",")); // DESC: acc,ab,aaaa,aaa,a,ACC,AB,AAA,A
I guess no surprise.
Case insensitive
Use toLowerCase()
or toUpperCase()
if you want to ignore the difference between upper case and lower case.
function ascendingCaseIgnore(a: string, b: string) {
const strA = a.toLowerCase();
const strB = b.toLowerCase();
if (strA > strB) {
return 1;
}
if (strA < strB) {
return -1;
}
return 0;
}
console.log(strs.sort(ascendingCaseIgnore).join(",")); // ASC: a,A,aaa,AAA,aaaa,ab,AB,acc,ACC
Number string array
If the array is a number string array, the result might not be your desired output.
const strNums = ["27", "24", "100", "1", "002"];
console.log(strNums.sort().join(",")); // 002,1,100,24,27
Sort numerical order
If you want to sort it in numerical order, you have to convert it to number.
console.log(strNums.sort((a, b) => {
const numA = parseInt(a, 10);
const numB = parseInt(b, 10);
if (numA > numB) {
return 1;
}
if (numA < numB) {
return -1;
}
return 0;
}).join(",")); // 1,002,24,27,100
Take leading 0 into account
If you want to take leading 0 into account, you can implement it in the following way.
console.log(strNums.sort((a, b) => {
const isLeadingZeroA = a.startsWith("0");
const isLeadingZeroB = b.startsWith("0");
if (isLeadingZeroA || isLeadingZeroB) {
if (a > b) {
return 1;
}
if (a < b) {
return -1;
}
return 0;
}
const numA = parseInt(a, 10);
const numB = parseInt(b, 10);
if (numA > numB) {
return 1;
}
if (numA < numB) {
return -1;
}
return 0;
}).join(",")); // 002,1,24,27,100
Number string and non number string mixed
The basic sort order is as follows.
const strNumsMixed = ["27", "0xff", "100", "1", "002", "CCC", "AA", "aB"]
console.log(strNumsMixed.sort().join(",")); // 002,0xff,1,100,27,AA,CCC,aB
If you want to handle Hex as string but decimal number strings as numbers…
function sortStringFirst(a: string, b: string) {
const regex = /[-+]??\d+/;
const isNumA = regex.test(a);
const isNumB = regex.test(b);
if (isNumA && !isNumB) {
return 1;
}
if (!isNumA && isNumB) {
return -1;
}
if (isNumA && isNumB) {
const numA = parseInt(a, 10);
const numB = parseInt(b, 10);
return numA - numB;
// if (numA > numB) {
// return 1;
// }
// if (numA < numB) {
// return -1;
// }
// return 0;
}
return null;
}
function sortNumberFirst(a: string, b: string) {
const regex = /^[-+]??\d+$/;
const isNumA = regex.test(a);
const isNumB = regex.test(b);
if (isNumA && !isNumB) {
return -1;
}
if (!isNumA && isNumB) {
return 1;
}
if (isNumA && isNumB) {
const numA = parseInt(a, 10);
const numB = parseInt(b, 10);
return numA - numB;
// if (numA > numB) {
// return 1;
// }
// if (numA < numB) {
// return -1;
// }
// return 0;
}
return null;
}
console.log(strNumsMixed.sort((a, b) => {
const result = sortStringFirst(a, b);
if (result === null) {
return ascending(a, b);
}
return result;
}).join(",")); // AA,CCC,aB,0xff,1,002,27,100
console.log(strNumsMixed.sort((a, b) => {
const result = sortNumberFirst(a, b);
if (result === null) {
return ascending(a, b);
}
return result;
}).join(",")); // 1,002,27,100,0xff,AA,CCC,aB
It checks whether both values are number or not. If yes, compare them as number. If one of them is a string, do the following comparison depending on your needs.
// string first >>>
if (isNumA && !isNumB) {
return 1;
}
if (!isNumA && isNumB) {
return -1;
}
// <<<
// number first >>>
if (isNumA && !isNumB) {
return -1;
}
if (!isNumA && isNumB) {
return 1;
}
// <<<
If both are strings, do the comparison defined in the earlier section.
Number with Leading Alphabets and number string mixed array
Do you need to order something like this? [3, 4, "A1", "B3", "AB5"]
const extra = [
"A1", "1", "A10", "A11", "A12", "5", "3", "10", "A2",
"AB2", "A3", "A4", "B10", "B2", "F1", "F12", "F3",
];
console.log(extra.sort((a, b) => {
const numeric = /^[-+]??\d+/;
const isNumericA = numeric.test(a);
const isNumericB = numeric.test(b);
// e.g. compare 5 and 6
if (isNumericA && isNumericB) {
return parseInt(a, 10) - parseInt(b, 10);
}
// e.g. 5 and A2
if (isNumericA && !isNumericB) {
return -1;
}
// e.g. A2 and 6
if (!isNumericA && isNumericB) {
return 1;
}
const alphabets = /^[a-zA-Z]+/;
// Alphabet + number: A1, B3...
const aAlphabets = a.replace(/\d+/g, "");
const bAlphabets = b.replace(/\d+/g, "");
if (aAlphabets === bAlphabets) {
// e.g. Compare AB10 and AB12
const aNumber = a.replace(alphabets, "");
const bNumber = b.replace(alphabets, "");
// e.g. Compare 10 and 12 for AB10 and AB12
const result = aNumber === bNumber ? 0 : parseInt(aNumber, 10) - parseInt(bNumber, 10);
console.log(`A: ${a}, B: ${b}, result: ${result}`)
return result;
}
// e.g. A12 and B12
return aAlphabets > bAlphabets ? 1 : -1;
}).join(","));
// 1,3,5,10,A1,A2,A3,A4,A10,A11,A12,AB2,B2,B10,F1,F3,F12
What does the last part do? It removes number part and then compares the two alphabets. If it’s the same alphabets/string, it removes the alphabet part and compares the number part. The comment might help you understand the logic.
Comments
Thanks!