对字母数字字符串进行排序 - 字母在前

Sort alphanumeric strings - Letters first

提问人:Sarah Trees 提问时间:11/2/2023 更新时间:11/2/2023 访问量:106

问:

var List = ["F", "60", "59", "6", "7", "7'", "60'", "60a", "c1", "A", "5", "a1", "6.2", "A'", "B", "A1"];

var sortedListe = List.sort(function (a, b) {
  var collator = new Intl.Collator('en', { numeric: true, sensitivity: "base" });
  return collator.compare(a, b);
});

console.log(sortedListe);

我得到什么: [“5”, “6”, “6.2”, “7”, “7'”, “59”, “60”, “60'”, “60a”, “A”, “A'”, “a1”, “A1”, “B”, “c1”, “F”]

https://jsfiddle.net/5coraw0z/

优点:6.2 在 7 之前;7' 也在正确的位置;c1 介于 B 和 F 之间。 CONTRA:以字母开头的字符串位于列表的末尾。

我的预期结果: [“A”, “A'”, “a1”, “A1”, “B”, “c1”, “F”, “5”, “6”, “6.2”, “7”, “7'”, “59”, “60”, “60'”, “60a”]

JavaScript 数组排序 字母数字

评论

0赞 pilchard 11/2/2023
以数字开头的字符串始终在字母排序中以字母开头的字符串之前排序。你的订单是非规范的,所以你需要编写自己的比较器
2赞 pilchard 11/2/2023
这回答了你的问题吗?对字符串数组进行排序,但数字排在最后
0赞 prasad_ 11/2/2023
您可以引入此附加代码来获取结果(但该解决方案不使用单个比较器)。var ix = sortedListe.findIndex(e => /^[a-zA-Z()]+$/.test(e.charAt(0))); var result = sortedListe.slice(ix).concat(sortedListe.splice(0, ix));
0赞 Nina Scholz 11/2/2023
顺便说一句,您对这样的值期望什么顺序?使用的排序器通过点和字母/数字字符的变化来分隔。如果您喜欢按数值排序,则顺序会有所不同。['6.2', '6.10']
0赞 epascarello 11/2/2023
stackoverflow.com/questions/4340227/......有想法,不完全匹配。

答:

2赞 gog 11/2/2023 #1

如果你需要非标准的排序规则,通常更容易单独对数组的每个部分进行排序,而不是发明一个复杂的排序回调:

function partition(ary, fn) {
    let res = [[], []]
    for (let x of ary)
        res[fn(x) ? 1 : 0].push(x)
    return res
}

let List = ["F", "60", "59", "6", "7", "7'", "60'", "60a", "c1", "A", "5", "a1", "6.2", "A'", "B", "A1"];

let collator = new Intl.Collator('en', { numeric: true, sensitivity: "base" });

let sortedListe = [].concat(
  ...partition(List, x => !isNaN(x[0]))
    .map(a => a.sort(collator.compare)))

console.log(...sortedListe);

评论

1赞 pilchard 11/2/2023
我同意分区可能很有用,但您已经定义了额外的排序条件,所以它可能只是(这只是对副本的改进)List.sort((a, b) => { ... return !isNaN(a[0]) - !isNaN(b[0]) || collator.compare(a, b)})
0赞 Alexander Nenashev 11/2/2023
isNaN(' ') === 假
0赞 pilchard 11/3/2023
@AlexanderNenashev,副本中也涵盖了这一点,但 OP 示例数据没有以空格开头的字符串。
2赞 Alexander Nenashev 11/2/2023 #2

有了这种数据,我们可以避免整理器。逻辑是逐个字符对项目进行排序,并尽可能多地获取数字字符。乍一看,它可能看起来很复杂,但这只是与字母/数字/未定义情况的逐个字符比较。

目标是速度,以避免任何中间阵列,如gog的soluiton。

还有一个重要的区别,即 gog 的解决方案将所有内容分成 2 个字母和数字字符串进行比较。虽然我的解决方案接受任意数量的字母/数字字符串组合。

const list = ["F", "60", "59", "6", "7", "7'", "60a", "60'", "c1", "A", "5", "a1", "6.2", "A'", "B", "A1"];

// fetch as much as possible digits from a position in a string plus adjust the position
const gainNum = (str, idx) => {
  let num = '';
  for(;idx < str.length; idx++){
    const c = str[idx];
    if( c < '0' || c > '9' ) break;
    num += c;
  }
  return {num, idx};
}

function sort(aa, bb, ai = 0, bi = 0) {

  // compare numbers
  let anum, bnum;
  ({num: anum, idx: ai} = gainNum(aa, ai));
  ({num: bnum, idx: bi} = gainNum(bb, bi));

  if(anum) return bnum ? anum - bnum || sort(aa, bb, ai, bi) : 1; else if(bnum) return -1;

  // compare string lengths
  if(ai===aa.length) return  bi < bb.length ? -1 : 0; else if(bi === bb.length) return 1;

  // compare alpha chars
  let a = aa[ai].toLowerCase();
  let b = bb[bi].toLowerCase();
  return a === b ? sort(aa, bb, ai + 1, bi + 1) : a > b ? 1 : -1;
}

console.log('expected:', ...["A", "A'", "a1", "A1", "B", "c1", "F", "5", "6", "6.2", "7", "7'", "59", "60", "60'", "60a"]);

console.log('sorted  :', ...list.slice().sort(sort));

` Chrome/118
------------------------------------------------------
Alexander   1.00x  |  x100000  150  156  182  188  190
gog         4.40x  |  x100000  660  665  666  670  672
------------------------------------------------------
https://github.com/silentmantra/benchmark `

const list = ["F", "60", "59", "6", "7", "7'", "60a", "60'", "c1", "A", "5", "a1", "6.2", "A'", "B", "A1"];

// @benchmark gog

function partition(ary, fn) {
    let res = [[], []]
    for (let x of ary)
        res[fn(x) ? 1 : 0].push(x)
    return res
}

let collator = new Intl.Collator('en', { numeric: true, sensitivity: "base" });

[].concat(
  ...partition(list, x => !isNaN(x[0]))
    .map(a => a.sort(collator.compare)))

// @benchmark Alexander

// fetch as much as possible digits from a position in a string plus adjust the position
const gainNum = (str, idx) => {
  let num = '';
  for(;idx < str.length; idx++){
    const c = str[idx];
    if( c < '0' || c > '9' ) break;
    num += c;
  }
  return {num, idx};
}

function sort(aa, bb, ai = 0, bi = 0) {

  // compare numbers
  let anum, bnum;
  ({num: anum, idx: ai} = gainNum(aa, ai));
  ({num: bnum, idx: bi} = gainNum(bb, bi));

  if(anum) return bnum ? anum - bnum || sort(aa, bb, ai, bi) : 1; else if(bnum) return -1;

  // compare string lengths
  if(ai===aa.length) return  bi < bb.length ? -1 : 0; else if(bi === bb.length) return 1;

  // compare alpha chars
  let a = aa[ai].toLowerCase();
  let b = bb[bi].toLowerCase();
  return a === b ? sort(aa, bb, ai + 1, bi + 1) : a > b ? 1 : -1;
}


list.slice().sort(sort);

/*@end*/eval(atob('e2xldCBlPWRvY3VtZW50LmJvZHkucXVlcnlTZWxlY3Rvcigic2NyaXB0Iik7aWYoIWUubWF0Y2hlcygiW2JlbmNobWFya10iKSl7bGV0IHQ9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7dC5zcmM9Imh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9naC9zaWxlbnRtYW50cmEvYmVuY2htYXJrL2xvYWRlci5qcyIsdC5kZWZlcj0hMCxkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKHQpfX0='));

评论

1赞 epascarello 11/2/2023
太复杂了......
0赞 Alexander Nenashev 11/2/2023
@epascarello乍一看,但实际上它只是逐个字符排序,没有什么非常复杂的
0赞 Alexander Nenashev 11/2/2023
@epascarello目标是速度
0赞 pilchard 11/3/2023
整理器的好处是跨语言的广泛字符支持。放入重音字符。"ā1"
0赞 Alexander Nenashev 11/3/2023
@pilchard同意,对于 alpha 字符,无论如何我们都可以使用整理器
0赞 dreamer 12/4/2023 #3

表示函数和对象之间的实现等价性(参见 @ut8pia/分类器库)可以立即提供预期的顺序。reprTrueSetrepresentation-based

摆姿势后:

const 
    data = ["F", "60", "59", "6", "7", "7'", "60'", "60a", "c1", "A", ...],
    expected = ["A", "A'", "a1", "A1", "B", "c1", "F", "5", "6", "6.2", ...],
    collator = new Intl.Collator('en', { 
        numeric: true, 
        sensitivity: 'case', 
        caseFirst: 'lower'
    })

这些项目将添加到:TrueSet

const
    repr = item => [Number.parseInt(item[0] + 1)? 1: 0],
    set = TrueSet.of(repr, ORDER.ASCENDING, collator.compare.bind(collator))
        .letAll(data);

assert.deepEqual(Array.from(set), Array.from(new Set(expected)))

而且,瞧,达到了预期的订单。

该函数对项目进行分类,分配给以数字开头的项目和其他项目。创建者中的第一个比较器对这些类进行排序,而在每个类中,顺序由第二个比较器确定。repr10ORDER.ASCENDINGTrueSetcollator.compare

需要注意的是,不会迭代重复的项目。但是,它保留了每个项目的出现次数,可通过该方法访问TrueSetn(item)

const
    got = Array.from(set)
        .map(item => new Array(set.n(item)).fill(item))
            .flat();

assert.deepEqual(got, expected)