JavaScript: Natural Sort of Alphanumerical Strings

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 and false; the default is false. This option can be set through an options property or through a Unicode extension key; if both are provided, the options 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



Leave a reply



Submit