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

Answers:

Answer

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

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

Answer

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

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|

Answer

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/

Answer

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.

Answer

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)

Answer

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.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.