Javascript:自然的字母数字字符串


173

我正在寻找一种对包含数字和文本以及它们的组合的数组进行排序的最简单方法。

例如

'123asd'
'19asd'
'12345asd'
'asd123'
'asd12'

变成

'19asd'
'123asd'
'12345asd'
'asd12'
'asd123'

这将与我在这里提出的另一个问题的解决方案结合使用。

排序功能本身可以工作,我需要的功能是可以说'19asd'小于'123asd'。

我用JavaScript编写。

编辑:正如adormitu指出的,我正在寻找的是用于自然排序的函数


还看到How do you do string comparison in JavaScript?stackoverflow.com/questions/51165/...
阿德里安要

1
最初的问题是在2010年提出的,所以这并不奇怪:)
ptrn

Answers:


316

现在,这在使用localeCompare的现代浏览器中成为可能。通过传递numeric: true选项,它将智能识别数字。您可以使用不区分大小写sensitivity: 'base'。已在Chrome,Firefox和IE11中测试。

这是一个例子。返回1,表示10在2之后:

'10'.localeCompare('2', undefined, {numeric: true, sensitivity: 'base'})

为了提高排序大量字符串时的性能,本文说:

比较大量字符串时,例如在对大型数组进行排序时,最好创建一个Intl.Collat​​or对象,并使用其compare属性提供的功能。文件连结

var collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
var myArray = ['1_Document', '11_Document', '2_Document'];
console.log(myArray.sort(collator.compare));


12
如果要对对象数组进行排序,还可以使用整理器:codepen.io/TimPietrusky/pen/rKzoGN
TimPietrusky

2
为了澄清以上注释:“如果未提供locales参数或未定义locales参数,则使用运行时的默认语言环境。”
gkiely

46

所以你需要自然的排序吗?

如果是这样,那么布莱恩·惠斯曼(Brian Huisman)的这个脚本可能是基于大卫·科勒(David Koelle)的作品那么您可能需要。

看来Brian Huisman的解决方案现在直接托管在David Koelle的博客上:


我正在寻找正确,自然的排序方式。我将查看您发送的链接,谢谢
ptrn 2010年

那是非常不自然的。它不会产生字母排序。
tchrist 2011年

@tchrist:“它不产生字母排序”是什么意思?
Adrien 2014年

它工作正常,但不能正确处理负数。即:它将产生['-1'。'-2','0','1','2']。
adrianboimvaser 2014年

2
@mhitza这个代码似乎做得很好github.com/litejs/natural-compare-lite看到快速测试jsbin.com/bevututodavi/1/edit?js,console
Adrien Be

23

要比较值,您可以使用比较方法-

function naturalSorter(as, bs){
    var a, b, a1, b1, i= 0, n, L,
    rx=/(\.\d+)|(\d+(\.\d+)?)|([^\d.]+)|(\.\D+)|(\.$)/g;
    if(as=== bs) return 0;
    a= as.toLowerCase().match(rx);
    b= bs.toLowerCase().match(rx);
    L= a.length;
    while(i<L){
        if(!b[i]) return 1;
        a1= a[i],
        b1= b[i++];
        if(a1!== b1){
            n= a1-b1;
            if(!isNaN(n)) return n;
            return a1>b1? 1:-1;
        }
    }
    return b[i]? -1:0;
}

但是,为了加快排序数组的速度,请在排序之前绑定数组,因此您只需要执行一次小写转换和正则表达式即可,而不必进行排序的每一步。

function naturalSort(ar, index){
    var L= ar.length, i, who, next, 
    isi= typeof index== 'number', 
    rx=  /(\.\d+)|(\d+(\.\d+)?)|([^\d.]+)|(\.(\D+|$))/g;
    function nSort(aa, bb){
        var a= aa[0], b= bb[0], a1, b1, i= 0, n, L= a.length;
        while(i<L){
            if(!b[i]) return 1;
            a1= a[i];
            b1= b[i++];
            if(a1!== b1){
                n= a1-b1;
                if(!isNaN(n)) return n;
                return a1>b1? 1: -1;
            }
        }
        return b[i]!= undefined? -1: 0;
    }
    for(i= 0; i<L; i++){
        who= ar[i];
        next= isi? ar[i][index] || '': who;
        ar[i]= [String(next).toLowerCase().match(rx), who];
    }
    ar.sort(nSort);
    for(i= 0; i<L; i++){
        ar[i]= ar[i][1];
    }
}

在我的情况下,内部数组决定外部数组的顺序是否可行?
ptrn

什么String.prototype.tlc()啊 这是您自己的代码还是从某个地方获得的?如果是后者,请链接到该页面。
Andy E 2010年

很抱歉改正了错误,谢谢。如果希望a [1]和b [1]控制排序,请使用a = String(a [1])。toLowerCase();。b = String(b [1])。toLowerCase();
肯纳贝克

我只有一个要排序的数据列表,认为应该可以在Chrome Dev Tools控制台中轻松完成-感谢您提供的功能!
2013年

9

如果您有一组对象,可以这样:

myArrayObjects = myArrayObjects.sort(function(a, b) {
  return a.name.localeCompare(b.name, undefined, {
    numeric: true,
    sensitivity: 'base'
  });
});


1
完美的答案!谢谢。
hubert17

5

截至2019年,功能最齐全的图书馆似乎很自然

const { orderBy } = require('natural-orderby')

const unordered = [
  '123asd',
  '19asd',
  '12345asd',
  'asd123',
  'asd12'
]

const ordered = orderBy(unordered)

// [ '19asd',
//   '123asd',
//   '12345asd',
//   'asd12',
//   'asd123' ]

它不仅接受字符串数组,而且还可以按对象数组中某个键的值进行排序。它还可以自动识别并排序以下字符串:货币,日期,货币和其他一堆东西。

令人惊讶的是,压缩后它也只有1.6kB。


2

想象一下一个8位数的填充函数可以转换为:

  • '123asd'->'00000123asd'
  • '19asd'->'00000019asd'

我们可以使用填充字符串来帮助我们对“ 19asd”进行排序,使其出现在“ 123asd”之前。

使用正则表达式/\d+/g可以帮助找到所有需要填充的数字:

str.replace(/\d+/g, pad)

下面演示了使用此技术进行排序:

var list = [
    '123asd',
    '19asd',
    '12345asd',
    'asd123',
    'asd12'
];

function pad(n) { return ("00000000" + n).substr(-8); }
function natural_expand(a) { return a.replace(/\d+/g, pad) };
function natural_compare(a, b) {
    return natural_expand(a).localeCompare(natural_expand(b));
}

console.log(list.map(natural_expand).sort()); // intermediate values
console.log(list.sort(natural_compare)); // result

中间结果显示了natural_expand()例程的功能,并让您了解了随后的natural_compare例程将如何工作:

[
  "00000019asd",
  "00000123asd",
  "00012345asd",
  "asd00000012",
  "asd00000123"
]

输出:

[
  "19asd",
  "123asd",
  "12345asd",
  "asd12",
  "asd123"
]

1

在上面@Adrien Be的答案的基础上,并使用Brian HuismanDavid koelle创建的代码,这是对对象数组进行排序的修改后的原型:

//Usage: unsortedArrayOfObjects.alphaNumObjectSort("name");
//Test Case: var unsortedArrayOfObjects = [{name: "a1"}, {name: "a2"}, {name: "a3"}, {name: "a10"}, {name: "a5"}, {name: "a13"}, {name: "a20"}, {name: "a8"}, {name: "8b7uaf5q11"}];
//Sorted: [{name: "8b7uaf5q11"}, {name: "a1"}, {name: "a2"}, {name: "a3"}, {name: "a5"}, {name: "a8"}, {name: "a10"}, {name: "a13"}, {name: "a20"}]

// **Sorts in place**
Array.prototype.alphaNumObjectSort = function(attribute, caseInsensitive) {
  for (var z = 0, t; t = this[z]; z++) {
    this[z].sortArray = new Array();
    var x = 0, y = -1, n = 0, i, j;

    while (i = (j = t[attribute].charAt(x++)).charCodeAt(0)) {
      var m = (i == 46 || (i >=48 && i <= 57));
      if (m !== n) {
        this[z].sortArray[++y] = "";
        n = m;
      }
      this[z].sortArray[y] += j;
    }
  }

  this.sort(function(a, b) {
    for (var x = 0, aa, bb; (aa = a.sortArray[x]) && (bb = b.sortArray[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.sortArray.length - b.sortArray.length;
  });

  for (var z = 0; z < this.length; z++) {
    // Here we're deleting the unused "sortArray" instead of joining the string parts
    delete this[z]["sortArray"];
  }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.