19

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...",...]
3

10 Answers 10

18

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.

Sign up to request clarification or add additional context in comments.

Comments

16

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("");
}

5 Comments

Is there any difference between the 2? My 1st impression b4 testing them out were, maybe the 2nd (Opera) 1 will be more reliable, since its from Opera, however after testing, jsfiddle.net/sqcFD, I found out that I got an error in that 1. Maybe the shorter 1 will work for me.
@jiewmeng I only tested the first one - I assumed (based on the depth of analysis and a quick glance over the code) that the second one was solid... Stick with the first one if it's working for you.
Note Array.prototype.alphanumSort requires that the input array contain only strings.
@JiewMeng "maybe the 2nd (Opera) 1 will be more reliable, since its from Opera" : it's not from Opera, it was only a blog by GreyWyvern (Brian Huisman) on the Opera blog hosting service. see web.archive.org/web/20130826203933/http://my.opera.com/…
Also note that Brian Huisman was inspired by Dave Koelle's Alphanum Algorithm on web.archive.org/web/20131005224909/http://www.davekoelle.com/…
15

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".

8 Comments

If I have a number in a string "55" it sorts wrongly, jsfiddle.net/8VjWL, it generally works tho
@jiewmeng This was not part of your question. Further, if you have a number in a string...then you have a string and not a number, and you should be populating your array more precisely. :p (You could add *1 or parseFloat as your first sort criteria if you really wanted, but I would encourage you to do this only if you really must accept arrays with numbers-as-strings.)
+1 definitely simpler code for the cleaner cases where no numbers are masquerading as strings.
Although my answer is short, it is annoying to type all those fallback cases explicitly. I've written Array.sortBy as a convenience for this sort of thing. You would use it with this problem as: arr.sortBy( function(o){ return [typeof o, o] } );
short and sweet :)
|
7

// 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)

2 Comments

It looks similar to jball's first example, because I am aka mrhoo.
This should work, but is potentially slow for huge arrays because of use of regex :/
6

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|

5 Comments

A quick profile session in chrome shows this answer as the fastest. It's marginally faster than Phrogz solution and a magnitude faster than either of jball's solutions.
I measured a 10% speed increase by using temp vars var as = isNaN(a), bs = isNaN(b);
Easy see that regex swallows processing. Maybe the typeof either?
Nice - hopefully this isn't being used in situations that are that performance critical, but a speedup from code that is as readable as this is good news all around.
@pinichi - when testing it I noticed you used a=b in your string comparison function; I'm assuming that you meant a==b (which seems to work correctly for me) and are not using some arcane js assigment as comparison trick?
2

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/

1 Comment

I'm not sure that I'd categorize that as 'simple' :)
1

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.

Comments

1

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)

Comments

0
var sorted =  ['as', '21sasa0', 'bssll'].sort((a,b) => a.replace(/[0-9]/g,"").localeCompare(b.replace(/[0-9]/g,"")));

1 Comment

Welcome to Stack Overflow! Please read How to Answer and edit your question to contain an explanation as to why this code would actually solve the problem at hand. Always remember that you're not only solving the problem, but are also educating the OP and any future readers of this post.
0

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);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.