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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2802341/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 01:59:58  来源:igfitidea点击:

Javascript : natural sort of alphanumerical strings

javascriptsortingnatural-sort

提问by ptrn

I'm looking for the easiest way to sort an array that consists of numbers and text, and a combination of these.

我正在寻找对由数字和文本及其组合组成的数组进行排序的最简单方法。

E.g.

例如

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

turns into

变成

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

This is going to be used in combination with the solution to another question I've asked here.

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

The sorting function in itself works, what I need is a function that can say that that '19asd' is smaller than '123asd'.

排序功能本身有效,我需要的是一个可以说“19asd”小于“123asd”的功能。

I'm writing this in JavaScript.

我正在用 JavaScript 写这个。

Edit: as adormitupointed out, what I'm looking for is a function for natural sorting

编辑:正如adormitu指出的那样,我正在寻找的是自然排序的功能

回答by frodo2975

This is now possible in modern browsers using localeCompare. By passing the numeric: trueoption, it will smartly recognize numbers. You can do case-insensitive using sensitivity: 'base'. Tested in Chrome, Firefox, and IE11.

这现在可以在使用 localeCompare 的现代浏览器中实现。通过传递numeric: true选项,它将巧妙地识别数字。您可以使用sensitivity: 'base'. 在 Chrome、Firefox 和 IE11 中测试。

Here's an example. It returns 1, meaning 10 goes after 2:

这是一个例子。它返回1,这意味着 10 在 2 之后:

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

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

For performance when sorting large numbers of strings, the article says:

对于排序大量字符串时的性能,文章说:

When comparing large numbers of strings, such as in sorting large arrays, it is better to create an Intl.Collator object and use the function provided by its compare property. Docs link

在比较大量字符串时,例如对大型数组进行排序时,最好创建一个 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));

回答by mhitza

So you need a natural sort?

所以你需要一个自然的排序

If so, than maybe this script by Brian Huisman based on David koelle's workwould be what you need.

如果是这样,那么也许Brian Huisman 基于 David koelle 的工作编写的这个脚本将是您所需要的。

It seems like Brian Huisman's solution is now directly hosted on David Koelle's blog:

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

回答by kennebec

To compare values you can use a comparing method-

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

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

But for speed in sorting an array, rig the array before sorting, so you only have to do lower case conversions and the regular expression once instead of in every step through the sort.

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

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

回答by Julien

The most fully-featured library to handle this as of 2019 seems to be natural-orderby.

截至 2019 年,处理此问题的功能最齐全的库似乎是natural-orderby

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

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

const ordered = orderBy(unordered)

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

It not only takes arrays of strings, but also can sort by the value of a certain key in an array of objects. It can also automatically identify and sort strings of: currencies, dates, currency, and a bunch of other things.

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

Surprisingly, it's also only 1.6kB when gzipped.

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

回答by D0rm1nd0

If you have a array of objects you can do like this:

如果你有一个对象数组,你可以这样做:

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

var myArrayObjects = [{
    "id": 1,
    "name": "1 example"
  },
  {
    "id": 2,
    "name": "100 example"
  },
  {
    "id": 3,
    "name": "12 example"
  },
  {
    "id": 4,
    "name": "5 example"
  },

]

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

回答by Stephen Quan

Imagine an 8 digit padding function that transforms:

想象一个 8 位填充函数,它可以转换:

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

We can used the padded strings to help us sort '19asd' to appear before '123asd'.

我们可以使用填充的字符串来帮助我们将“19asd”排序为出现在“123asd”之前。

Use the regular expression /\d+/gto help find all the numbers that need to be padded:

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

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

The following demonstrates sorting using this technique:

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

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

The intermediate results show what the natural_expand() routine does and gives you an understanding of how the subsequent natural_compare routine will work:

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

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

Outputs:

输出:

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

回答by Eric Norcross

Building on @Adrien Be's answer above and using the code that Brian Huisman& David koellecreated, here is a modified prototype sorting for an array of objects:

以上面@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"];
  }
}