# Sort Array Elements (string with numbers), natural sort

I have an array like;

``````["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"]
``````

And need to sort it so it appears like;

``````["IL0 Foo", "IL3 Bob says hello", "IL10 Baz", "PI0 Bar"]
``````

I have tried a sort function;

``````function compare(a,b) {
if (a < b)
return -1;
if (a > b)
return 1;
return 0;
}
``````

but this gives the order

``````["IL0 Foo", "IL10 Baz", "IL3 Bob says hello", "PI0 Bar"]
``````

I have tried to think of a regex that will work but can't get my head around it.
If it helps the format will always be 2 letters, x amount of numbers, then any number of characters.

This is called "natural sort" and can be implemented in JS like this:

``````function naturalCompare(a, b) {
var ax = [], bx = [];

a.replace(/(\d+)|(\D+)/g, function(_, \$1, \$2) { ax.push([\$1 || Infinity, \$2 || ""]) });
b.replace(/(\d+)|(\D+)/g, function(_, \$1, \$2) { bx.push([\$1 || Infinity, \$2 || ""]) });

while(ax.length && bx.length) {
var an = ax.shift();
var bn = bx.shift();
var nn = (an - bn) || an.localeCompare(bn);
if(nn) return nn;
}

return ax.length - bx.length;
}

/////////////////////////

test = [
"img12.png",
"img10.png",
"img2.png",
"img1.png",
"img101.png",
"img101a.png",
"abc10.jpg",
"abc10",
"abc2.jpg",
"20.jpg",
"20",
"abc",
"abc2",
""
];

test.sort(naturalCompare)
document.write("<pre>" + JSON.stringify(test,0,3));``````

To sort in reverse order, just swap the arguments:

``````test.sort(function(a, b) { return naturalCompare(b, a) })
``````

or simply

``````test = test.sort(naturalCompare).reverse();
``````

You could use `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 = ["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"];

array.sort(function (a,b) {
return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' });
});

console.log(array);``````

``````var re = /([a-z]+)(\d+)(.+)/i;
var arr = ["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"];
var order = arr.sort( function(a,b){
var ma = a.match(re),
mb = b.match(re),
a_str = ma,
b_str = mb,
a_num = parseInt(ma,10),
b_num = parseInt(mb,10),
a_rem = ma,
b_rem = mb;
return a_str > b_str ? 1 : a_str < b_str ? -1 : a_num > b_num ? 1 : a_num < b_num ? -1 : a_rem > b_rem;
});
``````

I liked georg's solution a lot, but I needed underscores ("_") to sort before numbers. Here's how I modified his code:

``````var chunkRgx = /(_+)|([0-9]+)|([^0-9_]+)/g;
function naturalCompare(a, b) {
var ax = [], bx = [];

a.replace(chunkRgx, function(_, \$1, \$2, \$3) {
ax.push([\$1 || "0", \$2 || Infinity, \$3 || ""])
});
b.replace(chunkRgx, function(_, \$1, \$2, \$3) {
bx.push([\$1 || "0", \$2 || Infinity, \$3 || ""])
});

while(ax.length && bx.length) {
var an = ax.shift();
var bn = bx.shift();
var nn = an.localeCompare(bn) ||
(an - bn) ||
an.localeCompare(bn);
if(nn) return nn;
}

return ax.length - bx.length;
}

/////////////////////////

test = [
"img12.png",
"img10.png",
"img2.png",
"img1.png",
"img101.png",
"img101a.png",
"abc10.jpg",
"abc10",
"abc2.jpg",
"20.jpg",
"20",
"abc",
"abc2",
"_abc",
"_ab_c",
"_ab__c",
"_abc_d",
"ab_",
"abc_",
"_ab_cd",
""
];

test.sort(naturalCompare)
document.write("<pre>" + JSON.stringify(test,0,3));``````

``````var naturalSort = function (a, b) {
a = ('' + a).replace(/(\d+)/g, function (n) { return ('0000' + n).slice(-5) });
b = ('' + b).replace(/(\d+)/g, function (n) { return ('0000' + n).slice(-5) });
return a.localeCompare(b);
}

var naturalSortModern = function (a, b) {
return ('' + a).localeCompare(('' + b), 'en', { numeric: true });
}

console.dir((["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"].sort(naturalSort)));

console.dir((["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"].sort(naturalSortModern)));``````

You could do a regex like this to get non-numeric and numeric parts of the string:

``````var s = "foo124bar23";
s.match(/[^\d]+|\d+/g)
``````

returns: `["foo", "124" , "bar" , "23"]`

Then in your compare function you can iterate through the parts of the two strings comparing them part-by-part. The first non-matching part determines the result of the overall comparison. For each part, check if the part starts with a digit and if so parse it as a number before doing the comparison.

Add one more alternative (why not):

``````var ary = ["IL0 Foo", "PI0 Bar", "IL10 Hello", "IL10 Baz", "IL3 Bob says hello"];

// break out the three components in to an array
// "IL10 Bar" => ['IL', 10, 'Bar']
function getParts(i){
i = i || '';
var parts = i.match(/^([a-z]+)([0-9]+)(\s.*)\$/i);
if (parts){
return [
parts,
parseInt(parts, 10),
parts
];
}
return []; // erroneous
}
ary.sort(function(a,b){
// grab the parts
var _a = getParts(a),
_b = getParts(b);

// trouble parsing (both fail = no shift, otherwise
// move the troubles element to end of the array)
if(_a.length == 0 && _b.length == 0) return 0;
if(_a.length == 0) return -1;
if(_b.length == 0) return 1;

// Compare letter portion
if (_a < _b) return -1;
if (_a > _b) return 1;
// letters are equal, continue...

// compare number portion
if (_a < _b) return -1;
if (_a > _b) return 1;
// numbers are equal, continue...

// compare remaining string
if (_a < _b) return -1;
if (_a > _b) return 1;
// strings are equal, continue...

// exact match
return 0;
});
``````

jsfiddle example

Not pretty, but check the first two char codes. If all equal parse and compare the numbers:

``````var arr = ["IL0 Foo", "IL10 Baz", "IL3 Bob says hello", "PI0 Bar"];
arr.sort(function (a1, b1) {
var a = parseInt(a1.match(/\d+/g), 10),
b = parseInt(b1.match(/\d+/g), 10),
letterA = a1.charCodeAt(0),
letterB = b1.charCodeAt(0),
letterA1 = a1.charCodeAt(1),
letterB1 = b1.charCodeAt(1);
if (letterA > letterB) {
return 1;
} else if (letterB > letterA) {
return -1;
} else {
if (letterA1 > letterB1) {
return 1;
} else if (letterB1 > letterA1) {
return -1;
}
if (a < b) return -1;
if (a > b) return 1;
return 0;
}
});
``````

Example