Skip to content

Instantly share code, notes, and snippets.

@ericcornelissen
Last active January 28, 2019 20:03
Show Gist options
  • Save ericcornelissen/407f7d7f518feacbea7ff0348caeae76 to your computer and use it in GitHub Desktop.
Save ericcornelissen/407f7d7f518feacbea7ff0348caeae76 to your computer and use it in GitHub Desktop.
JavaScript Array prototype method to sort strings containing numbers correctly
/**
* Sort an array of string alpha-numerically.
*
* @example
* // returns ['1', '2', '10', '20'] rather then ['1', '10', '2', '20']
* ['1', '20', '10', '2'].strSort();
*
* @return {array} The alpha-numerically sorted array.
* @license The-Unlicense
*/
Array.prototype.strSort = function() {
var cache = { '': -Infinity /* makes sure empty strings always come first */ };
var getStartNumber = function(str) {
// Return the cached value for the string if
// present. Check for `undefined` explicitly
// because the value can be `0` or `NaN`.
if(cache[str] !== undefined) {
return cache[str];
}
// Check for tabs/spaces at the start of the input
// string and return special values accordingly.
var char = str.charAt(0);
if(/\s/.test(char)) {
// Ensure spaces (`-1`) come after tabs (`-2`).
return char === ' ' ? -1 : -2
}
// Initialize a counter to keep track of what
// character in the string is being evaluated.
var i = -1;
// Initialize a string to rebuild to input string.
var substr = '';
// Create a variable to remember the last valid
// number that was found. Initialized to NaN for
// strings that don't start with a number.
var valid = NaN;
// Loop over the input string to find the number
// it starts with.
while(i++, i < str.length) {
// Update the substring with the next character
// in the string.
substr += str.charAt(i);
// Parse the substring to a number.
var num = Number(substr);
// Check if the number (`num`) is still a number.
if(isNaN(num)) {
break;
} else {
// Update the last valid number found, since
// the current substring is a valid number.
valid = num;
}
}
// Cache the number found for the string.
cache[str] = valid;
// Return the last valid number found.
return valid;
};
var compare = function(a, b) {
var aNum = getStartNumber(a),
bNum = getStartNumber(b);
// `aNum - bNum` is NaN whenever aNum || bNum is
// NaN. Otherwise both aNum and bNum are numbers.
if(isNaN(aNum - bNum)) {
// Given that `aNum - bNum` is NaN, then if
// `aNum` is not NaN, `bNum` must be NaN and
// so a < b.
if(!isNaN(aNum)) {
return -1;
}
// Given that `aNum - bNum` is NaN, then if
// `bNum` is not NaN, `aNum` must be NaN and
// so a > b.
if(!isNaN(bNum)) {
return 1;
}
// --> Continues to [1]
} else if(aNum === bNum) {
// When the numbers are negative, the original
// strings A and B started with spaces/tabs and
// the strings should be compared without them.
if(aNum < 0) {
return compare(a.substr(1), b.substr(1));
}
// Otherwise, Find the textual length of the
// number and get the remaining substrings for
// both of the input strings.
var numLength = String(aNum).length;
a = a.substr(numLength);
b = b.substr(numLength);
// --> Continues to [1]
} else {
// Otherwise, compare aNum and bNum using
// the regular integer comparison method.
return aNum - bNum;
}
// Otherwise, compare the two strings using the
// regular string comparison method. [1]
return a < b ? -1 : a > b ? 1 : 0;
}
return this.sort(compare);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment