Jiew Meng
Jiew Meng

Reputation: 88197

Sort Array of numeric & alphabetical elements (Natural Sort)

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

Answers (10)

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

zafar
zafar

Reputation: 1

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

Upvotes: 0

vegemite4me
vegemite4me

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

kennebec
kennebec

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

MH2K9
MH2K9

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

Phrogz
Phrogz

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

jball
jball

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

pinichi
pinichi

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

Shadow Wizard
Shadow Wizard

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

Erik  Reppen
Erik Reppen

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

Related Questions