Reputation: 88197
Suppose I have an array
var arr = [1,5,"ahsldk",10,55,3,2,7,8,1,2,75,"abc","huds"];
and I try sorting it, I get something like ...
[1, 1, 10, 2, 2, 3, 5, 55, 7, 75, 8, "abc", "ahsldk", "huds"]
notice 10 is before 2, how can I have something more like
[1,1,2,2,3,5 ..., "abc", "ahs...",...]
Upvotes: 18
Views: 28859
Reputation: 1
An alternative to the other answers here, using a combination of localCompare with other things:
function naturalSortObjects(list, property) {
function naturalCompare(a, b) {
var ax = [], bx = [];
a[property].replace(/(\d+)|(\D+)/g, function(_, $1, $2) { ax.push([$1 || Infinity, $2 || ""]) });
b[property].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[0] - bn[0]) || an[1].localeCompare(bn[1]);
if(nn) return nn;
}
return ax.length - bx.length;
}
return list.sort(naturalCompare);
}
Upvotes: 0
Reputation: 1
var sorted = ['as', '21sasa0', 'bssll'].sort((a,b) => a.replace(/[0-9]/g,"").localeCompare(b.replace(/[0-9]/g,"")));
Upvotes: 0
Reputation: 6856
You could do this in one line using String.prototype.localCompare()
and get the result you are looking for. Note that the numeric collation option is enabled.
var arr = [1,5,"ahsldk",10,55,3,2,7,8,1,2,75,"abc","huds"];
arr.sort((a,b) => ("" + a).localeCompare(b, undefined, {numeric: true}));
console.log(arr);
// [1, 1, 2, 2, 3, 5, 7, 8, 10, 55, 75, "abc", "ahsldk", "huds"]
Maybe add some logic to handle nulls.
Please be aware that this will only work with integer numbers. Floating point numbers will not be sorted the way you would hope.
Upvotes: 17
Reputation: 104760
// Most natural sorts are for sorting strings,
so file2
is sorted before file10
.
If you are mixing in actual numbers you need to sort them to the front of the array,
because negative numbers and digits separated by hyphens are a pain to interpret.
Strings with leading zeroes need to be careful, so part002
will sort before part010
.
var natSort=function(as, bs) {
var a, b, a1, b1,
rx= /(\d+)|(\D+)/g, rd= /\d/, rz=/^0/;
if(typeof as=='number' || typeof bs=='number'){
if(isNaN(as))return 1;
if(isNaN(bs))return -1;
return as-bs;
}
a= String(as).toLowerCase();
b= String(bs).toLowerCase();
if(a=== b) return 0;
if(!(rd.test(a) && rd.test(b))) return a> b? 1: -1;
a= a.match(rx);
b= b.match(rx);
while(a.length && b.length){
a1= a.shift();
b1= b.shift();
if(a1!== b1){
if(rd.test(a1) && rd.test(b1)){
return a1.replace(rz,'.0')- b1.replace(rz,'.0');
}
else return a1> b1? 1: -1;
}
}
return a.length - b.length;
}
array.sort(natSort)
Upvotes: 7
Reputation: 12039
I knew the following way also which might sort the array alphanumerically order.
const arr = [1, 5, "ahsldk", 10, 55, 3, 2, 7, 8, 1, 2, 75, "abc", "huds"];
arr.sort((a, b) => a - b || a.toString().localeCompare(b.toString()));
console.log(arr)
Upvotes: 1
Reputation: 303198
Short and sweet, per the original question:
var arr = [1,5,"ahsldk",10,55,3,2,7,8,1,2,75,"abc","huds"];
arr.sort(function(a,b){
var a1=typeof a, b1=typeof b;
return a1<b1 ? -1 : a1>b1 ? 1 : a<b ? -1 : a>b ? 1 : 0;
});
// [1, 1, 2, 2, 3, 5, 7, 8, 10, 55, 75, "abc", "ahsldk", "huds"]
(Sort first by type, then by value.)
A more-full-featured natural sort:
var items = ['a1c', 'a01', 'a1', 'a13', 'a1a', 'a1b', 'a3b1', 'a1b0',
'a1b3', 'a1b1', 'dogs', 'cats', 'hogs', 'a2', '2', '20',
1, 13, 1.1, 1.13, '1.2', 'a'];
console.log(naturalSort(items))
function naturalSort(ary, fullNumbers) {
var re = fullNumbers ? /[\d\.\-]+|\D+/g : /\d+|\D+/g;
// Perform a Schwartzian transform, breaking each entry into pieces first
for (var i=ary.length;i--;)
ary[i] = [ary[i]].concat((ary[i]+"").match(re).map(function(s){
return isNaN(s) ? [s,false,s] : [s*1,true,s];
}));
// Perform a cascading sort down the pieces
ary.sort(function(a,b){
var al = a.length, bl=b.length, e=al>bl?al:bl;
for (var i=1;i<e;++i) {
// Sort "a" before "a1"
if (i>=al) return -1; else if (i>=bl) return 1;
else if (a[i][0]!==b[i][0])
return (a[i][1]&&b[i][1]) ? // Are we comparing numbers?
(a[i][0]-b[i][0]) : // Then diff them.
(a[i][2]<b[i][2]) ? -1 : 1; // Otherwise, lexicographic sort
}
return 0;
});
// Restore the original values into the array
for (var i=ary.length;i--;) ary[i] = ary[i][0];
return ary;
}
With naturalSort
, pass true
as the second parameter if you want "1.13" to sort before "1.2".
Upvotes: 15
Reputation: 25014
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("");
}
Upvotes: 17
Reputation: 2205
This is a refined.
var arr = [1,5,"ahsldk",10,55,3,2,7,8,1,2,75,"56","abc","huds"];
arr.sort(
function (a,b){
if ( isNaN(a)&&isNaN(b)) return a<b?-1:a==b?0:1;//both are string
else if (isNaN(a)) return 1;//only a is a string
else if (isNaN(b)) return -1;//only b is a string
else return a-b;//both are num
}
);
result: 1|1|2|2|3|5|7|8|10|55|56|75|abc|ahsldk|huds|
Upvotes: 6
Reputation: 66389
If you have only alphabetical and integer items, you can stick with simple code:
var arr = [1,5,"ahsldk",10,55,3,2,7,8,1,2,75,"abc","huds"];
arr.sort(function(a, b)
{
if (a == b)
return 0;
var n1 = parseInt(a, 10);
var n2 = parseInt(b, 10);
if (isNaN(n1) && isNaN(n2)) {
//both alphabetical
return (a > b) ? 1 : 0;
}
else if (!isNaN(n1) && !isNaN(n2)) {
//both integers
return (n1 > n2) ? 1 : 0;
}
else if (isNaN(n1) && !isNaN(n2)) {
//a alphabetical and b is integer
return 1;
}
//a integer and b is alphabetical
return 0;
});
Working example: http://jsfiddle.net/25X2e/
Upvotes: 2
Reputation: 4635
If you can always assume numbers and strings of unmixed alphas, I would just divide and conquer. slice out numbers into a new array using typeof. Sort both independently and then just join the two arrays.
Upvotes: 1