Javascript : natural sort of alphanumerical strings
This is now possible in modern browsers using localeCompare. By passing the numeric: true
option, it will smartly recognize numbers. You can do case-insensitive using sensitivity: 'base'
. Tested in Chrome, Firefox, and IE11.
Here's an example. It returns 1
, meaning 10 goes after 2:
'10'.localeCompare('2', undefined, {numeric: true, sensitivity: 'base'})
For performance when sorting large numbers of strings, the article says:
When comparing large numbers of strings, such as in sorting large arrays, it is better to create an Intl.Collator object and use the function provided by its compare property. Docs link
var collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
var myArray = ['1_Document', '11_Document', '2_Document'];
console.log(myArray.sort(collator.compare));
Sort mixed alpha/numeric array
var reA = /[^a-zA-Z]/g;
var reN = /[^0-9]/g;
function sortAlphaNum(a, b) {
var aA = a.replace(reA, "");
var bA = b.replace(reA, "");
if (aA === bA) {
var aN = parseInt(a.replace(reN, ""), 10);
var bN = parseInt(b.replace(reN, ""), 10);
return aN === bN ? 0 : aN > bN ? 1 : -1;
} else {
return aA > bA ? 1 : -1;
}
}
console.log(
["A1", "A10", "A11", "A12", "A2", "A3", "A4", "B10", "B2", "F1", "F12", "F3"].sort(sortAlphaNum)
)
Alphanumerical / Natural Sort an Array with leading zeroes in Javascript
This should do it:
input.sort(({value:a}, {value:b}) => {
const ai = parseInt(a, 10), bi = parseInt(b, 10);
return (b == null) - (a == null)
|| (ai - bi)
|| (a > b) - (b > a);
}).map(x => x.value || "")
Sort Array of numeric & alphabetical elements (Natural Sort)
From http://snipplr.com/view/36012/javascript-natural-sort/ by mrhoo:
Array.prototype.naturalSort= function(){
var a, b, a1, b1, rx=/(\d+)|(\D+)/g, rd=/\d+/;
return this.sort(function(as, bs){
a= String(as).toLowerCase().match(rx);
b= String(bs).toLowerCase().match(rx);
while(a.length && b.length){
a1= a.shift();
b1= b.shift();
if(rd.test(a1) || rd.test(b1)){
if(!rd.test(a1)) return 1;
if(!rd.test(b1)) return -1;
if(a1!= b1) return a1-b1;
}
else if(a1!= b1) return a1> b1? 1: -1;
}
return a.length- b.length;
});
}
Or, from Alphanum: Javascript Natural Sorting Algorithm by Brian Huisman:
Array.prototype.alphanumSort = function(caseInsensitive) {
for (var z = 0, t; t = this[z]; z++) {
this[z] = [];
var x = 0, y = -1, n = 0, i, j;
while (i = (j = t.charAt(x++)).charCodeAt(0)) {
var m = (i == 46 || (i >=48 && i <= 57));
if (m !== n) {
this[z][++y] = "";
n = m;
}
this[z][y] += j;
}
}
this.sort(function(a, b) {
for (var x = 0, aa, bb; (aa = a[x]) && (bb = b[x]); x++) {
if (caseInsensitive) {
aa = aa.toLowerCase();
bb = bb.toLowerCase();
}
if (aa !== bb) {
var c = Number(aa), d = Number(bb);
if (c == aa && d == bb) {
return c - d;
} else return (aa > bb) ? 1 : -1;
}
}
return a.length - b.length;
});
for (var z = 0; z < this.length; z++)
this[z] = this[z].join("");
}
Javascript natural sort objects
You could use sorting with map and String#localeCompare
with options
sensitivity
Which differences in the strings should lead to non-zero result values. Possible values are:
"base"
: Only strings that differ in base letters compare as unequal. Examples:a ≠ b
,a = á
,a = A
."accent"
: Only strings that differ in base letters or accents and other diacritic marks compare as unequal. Examples:a ≠ b
,a ≠ á
,a = A
."case"
: Only strings that differ in base letters or case compare as unequal. Examples:a ≠ b
,a = á
,a ≠ A
."variant"
: Strings that differ in base letters, accents and other diacritic marks, or case compare as unequal. Other differences may also be taken into consideration. Examples:a ≠ b
,a ≠ á
,a ≠ A
.The default is "variant" for usage "sort"; it's locale dependent for usage "search".
numeric
Whether numeric collation should be used, such that "1" < "2" < "10". Possible values are
true
andfalse
; the default isfalse
. This option can be set through an options property or through a Unicode extension key; if both are provided, theoptions
property takes precedence. Implementations are not required to support this property.
var array = ['Ex 10', 'Ex 2', 'a 10.1', 'a 1.1', 'a 10.0', 'a 2.0'];
array.sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }));
console.log(array)
sort an alphanumeric array
The callback function returns positive or negative values depending on the comparison of the values. Just change the sign of the -1
and 1
values that are returned:
return an<bn?-1:(an>bn?1:(a[1]<b[1]?1:(a[1]>b[1]?-1:0)));
Here is a more readable way to write the same:
return (
an < bn ? -1 :
an > bn ? 1 :
a[1] < b[1] ? 1 :
a[1] > b[1] ? -1 :
0
);
Note that the original code sorts the numeric part descending and the rest of the string ascending, so this does the opposite. The value from the first two comparisons determine how the numeric part is sorted, and the last two determine how the rest of the strings are sorted, so you can adjust them to get the desired combination.
javascript natural sort
The solution is to use localeCompare()
, the options of which are here.
Now we can sort this array
const items = ['3rd', 'Apple', '24th', '99 in the shade', 'Dec3', 'Dec', 'Dec20', '10000', 'house', 'house11b', 'house11a', 'house99', '101', '$1.23'];
with one line of code
items.sort( ( a, b ) => a.localeCompare( b, navigator.languages[ 0 ] || navigator.language, { numeric: true, ignorePunctuation: true } ) );
yielding
['$1.23', '3rd', '24th', '99 in the shade', '101', '10000', 'Apple', 'Dec', 'Dec3', 'Dec20', 'house', 'house11a', 'house11b', 'house99']
Credit for this elegant solution belongs here.
EDIT
I'm not satisfied. This works, but only for strings. I'd like to have a sort that will chew on numbers and arrays too. I really want a one-size-fits-all, if I can get it. With that in mind, this update
items.sort( ( a, b ) => ( a + '' ).localeCompare( b, navigator.languages[ 0 ] || navigator.language, { numeric: true, ignorePunctuation: true } ) );
using this updated array (containing strings, integers, decimals and a nested array ['a',1,2])
let items = ['3rd', 'Apple', .99, '24th', 'apple.sauce', '99 in the shade', 99, 'Dec3', 'B', 'a', 'Dec', 'Dec20', 12, '10000', 'house', 1.24, '1.22', 'house11b', 1, 'house11a', 'house99', '101', 400.23, '$1.23', ['a',1,2]];
gets us closer with
['$1.23', 0.99, 1, '1.22', 1.24, '3rd', 12, '24th', 99, '99 in the shade', '101', 400.23, '10000', 'a', Array(3), 'Apple', 'apple.sauce', 'B', 'Dec', 'Dec3', 'Dec20', 'house', 'house11a', 'house11b', 'house99']
LASTLY
Now, to satisfy my own OCD tendencies, I want the currency value $1.23 to line up with the rest of the decimal values. Since indulging OCD often comes at a cost, I'm using this ugly ternary:
( a + '' ).substr( 0, 1 ) === '$' ? ( a + '' ).substr( 1, ( a + '' ).length ) : ( a + '' )
Also, I'll specify the language instead of grabbing it from the browser and set base sensitivity (which is language dependent).
So a case insensitive sort ignorePunctuation: true
that treats strings as numbers (as much as possible) numeric: true
, and respects the order of graphemes in the language sensitivity: 'base'
as well as sorts USD currency as decimals, is this
items.sort( ( a, b ) => ( ( a + '' ).substr( 0, 1 ) === '$' ? ( a + '' ).substr( 1, ( a + '' ).length ) : ( a + '' ) ).localeCompare( b, 'en', { ignorePunctuation: true, numeric: true, sensitivity: 'base' } ) );
which gives us this
[0.99, 1, '1.22', '$1.23', 1.24, '3rd', 12, '24th', 99, '99 in the shade', '101', 400.23, '10000', 'a', Array(3), 'Apple', 'apple.sauce', 'B', 'Dec', 'Dec3', 'Dec20', 'house', 'house11a', 'house11b', 'house99']
which is as close to a perfect one-size-fits-all as I need it to be. /p>
Naturally sort strings which contains numerics, letters and date and time in javascript
You could normalize date and time to ISO and sort by localeCompare
with options.
function normalize(s) {
return s
.replace(/(\d\d)(\/)(\d\d)(\/)(\d\d\d\d)/g, '$5-$3-$1')
.replace(/(\d{1,2}\.\d\d\.\d\d)\s([AP]M)/g, (_, t, m) => {
var p = t.split('.').map(Number);
if (p[0] === 12) {
p[0] = 0;
}
if (m === 'PM') {
p[0] += 12;
}
return p.map(v => v.toString().padStart(2 , '0')).join(':');
});
}
let documentData = [{ title:'01 Documents >File0010-Donia5,06/14/2018,1.14.03 PM.pdf' }, { title:'01 Documents >File0010-Donia5,06/14/2018,5.14.03 AM.pdf' }, { title:'04 Images > Image0010-image59323.jpg' }, { title:'04 Images > Image0010-image44005.jpg' }, { title:'01 Documents >File0010-Donia5,08/04/2018,5.14.03 PM.pdf' }, { title:'01 Documents >File0010-Donia5,12/14/2018,10.14.03 AM.pdf' }];
documentData.sort((a, b) => normalize(a.title).localeCompare(normalize(b.title), undefined, { numeric: true, sensitivity: 'base' }));
console.log(documentData);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Clever sorting (Sort strings that may or may not contain numbers)
Given the clarifications in comments, you can split each string by /(\d+)/g
and sort by each element in the resulting string array. Treat even indices as strings, and odd indices as numbers:
const input = ['unit 11', 'unit 1', 'unit 2', 'apt 11', 'apt 1', 'apt 2', 'unit A', 'unit c', 'unit B', 'apt a', 'apt C', 'apt b'];
function customSort (a, b) {
a = a.toUpperCase().split(/(\d+)/g);
b = b.toUpperCase().split(/(\d+)/g);
const length = Math.min(a.length, b.length);
for (let i = 0; i < length; i++) {
const cmp = (i % 2)
? a[i] - b[i]
: -(a[i] < b[i]) || +(a[i] > b[i]);
if (cmp) return cmp;
}
return a.length - b.length;
}
console.log(input.sort(customSort));
Related Topics
Convert a Unix Timestamp to Time in JavaScript
How to Check If Element Is Visible After Scrolling
Scroll to an Element With Jquery
Setstate Doesn't Update the State Immediately
When Should I Use a Return Statement in Es6 Arrow Functions
Understanding Unique Keys For Array Children in React.Js
Is It Safe to Expose Firebase APIkey to the Public
Preview an Image Before It Is Uploaded
Creating a Blob from a Base64 String in JavaScript
Safely Turning a Json String into an Object
How to Set/Unset a Cookie With Jquery
Get Difference Between 2 Dates in JavaScript
How to Access the Matched Groups in a JavaScript Regular Expression