对数字和字母元素进行排序(自然排序)的数组排序

17
Suppose I have an array
假设我有一个数组
var arr = [1,5,"ahsldk",10,55,3,2,7,8,1,2,75,"abc","huds"];

当我尝试对它进行排序时,我得到了类似于...

[1, 1, 10, 2, 2, 3, 5, 55, 7, 75, 8, "abc", "ahsldk", "huds"]

注意10在2之前,我该怎么做才能得到更像这样的结果

[1,1,2,2,3,5 ..., "abc", "ahs...",...]

4
你在寻找“自然排序”的术语。 - jball
3
请访问 https://dev59.com/tHE85IYBdhLWcg3wYicB,查看有关 JavaScript 自然排序字母数字字符串的内容。 - Adriano
自然排序(Natural sort order) - Peter Mortensen
10个回答

17

来源于http://snipplr.com/view/36012/javascript-natural-sort/ 作者是 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;
    });
}

或者,来自Alphanum:Brian Huisman 的JavaScript自然排序算法

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

这两者有什么区别吗?在测试它们之前,我的第一印象是,也许第二个(Opera)会更可靠,因为它来自Opera,但是在测试后,http://jsfiddle.net/sqcFD/,我发现我在那个里面得到了一个错误。也许较短的那个对我有用。 - Jiew Meng
@jiewmeng 我只测试了第一个 - 基于深度分析和快速浏览代码的假设,我认为第二个是可靠的... 如果第一个对你有效,请坚持使用它。 - jball
请注意,Array.prototype.alphanumSort 要求输入数组仅包含字符串。 - gradbot
1
@JiewMeng "也许第二个(Opera)更可靠,因为它来自Opera":它并不是来自Opera,只是由GreyWyvern(Brian Huisman)在Opera博客托管服务上发布的博客。请参见http://web.archive.org/web/20130826203933/http://my.opera.com/GreyWyvern/blog/show.dml/1671288 - Adriano
1
请注意,Brian Huisman 受到 Dave Koelle 在 https://web.archive.org/web/20131005224909/http://www.davekoelle.com/alphanum.html 上的 Alphanum 算法的启发。 - Adriano

16

您可以使用String.prototype.localeCompare()一行代码实现,并获得您要查找的结果。请注意,启用了数字排序选项。

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

可能需要添加一些逻辑来处理 null 值。

请注意此方法仅适用于整数。浮点数将无法按照您希望的方式进行排序。


15

简洁明了,如同原问题所述:

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

首先按类型排序,然后按值排序。


更完整的自然排序:

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

如果你想让"1.13"排在"1.2"前面,请在使用 naturalSort 时将第二个参数设置为 true


1
如果我有一个字符串中的数字 "55",它会排序错误,http://jsfiddle.net/8VjWL/,但通常它是有效的。 - Jiew Meng
2
@jiewmeng 这不是你的问题的一部分。而且,如果您在字符串中有一个数字...那么您有一个字符串,而不是一个数字,并且您应该更精确地填充您的数组。 :p (如果您真的想要,可以将*1parseFloat添加为第一个排序标准,但我鼓励您只在必须接受数字字符串数组时才这样做。) - Phrogz
+1 对于没有数字伪装成字符串的情况,更简洁的代码肯定更加清晰易懂。 - jball
虽然我的回答很短,但是明确地输入所有的fallback cases令人感到烦恼。我已经写了Array.sortBy来方便处理这种情况。你可以像这样使用它:arr.sortBy( function(o){ return [typeof o, o] } ); - Phrogz
1
short and sweet :) - user3025289
显示剩余3条评论

7

// 大多数自然排序都是针对字符串排序的, 因此file2会排在file10之前。

如果您混合实际数字,您需要将它们排序到数组的前面, 因为负数和由连字符分隔的数字很难解释。 具有前导零的字符串需要小心处理,因此part002会排在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)

1
它看起来与jball的第一个示例相似,因为我是mrhoo。 - kennebec
这应该可以工作,但由于使用了正则表达式,对于巨大的数组来说潜在地较慢 :/ - Jaro

6

这是一篇关于IT技术的文章。

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

结果:1|1|2|2|3|5|7|8|10|55|56|75|abc|ahsldk|huds|

说明:该结果是由多个元素组成的字符串,每个元素之间用竖线(|)分隔。其中,前十个元素是数字,后面三个元素是字符串。


在 Chrome 中进行快速的配置文件会话,显示此答案是最快的。它比 Phrogz 的解决方案略快,并且比 jball 的任何解决方案都快一个数量级。 - gradbot
1
我使用临时变量 var as = isNaN(a), bs = isNaN(b); 测得速度提高了10%。 - gradbot
很容易看出正则表达式会消耗处理能力。也许 typeof 也会有影响? - pinichi
很好 - 希望这不会在那些对性能要求非常高的情况下使用,但是由于代码可读性像这样的提速在所有方面都是好消息。 - jball
@pinichi - 在测试时我注意到你在字符串比较函数中使用了a=b; 我假设你的意思是a==b(对我来说似乎正常工作),而不是使用某些神秘的js赋值作为比较技巧? - jball

2

如果你只有字母和整数项,你可以使用简单的代码:

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

工作示例:http://jsfiddle.net/25X2e/

1
我不确定我会将其归类为“简单” :) - Phrogz

1
我知道以下方法可以按字母数字顺序对数组进行排序。

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)


1

如果你总是可以假设数字和字符串都没有混合使用,那么我会采用分治法。使用 typeof 从源数组中分离出数字并创建一个新的数组。然后独立排序这两个数组,最后再将它们合并。


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

2
欢迎来到Stack Overflow!请阅读[答案]并[编辑]您的问题,以包含有关此代码实际解决问题的说明。请记住,您不仅要解决问题,还要教育OP和任何未来的读者。 - Adriaan

0

这里提供一种不同的答案,使用localCompare与其他方法相结合:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接