提问人:Sarah Trees 提问时间:11/2/2023 更新时间:11/2/2023 访问量:106
对字母数字字符串进行排序 - 字母在前
Sort alphanumeric strings - Letters first
问:
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”]
答:
如果你需要非标准的排序规则,通常更容易单独对数组的每个部分进行排序,而不是发明一个复杂的排序回调:
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);
评论
List.sort((a, b) => { ... return !isNaN(a[0]) - !isNaN(b[0]) || collator.compare(a, b)})
有了这种数据,我们可以避免整理器。逻辑是逐个字符对项目进行排序,并尽可能多地获取数字字符。乍一看,它可能看起来很复杂,但这只是与字母/数字/未定义情况的逐个字符比较。
目标是速度,以避免任何中间阵列,如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"
表示函数和对象之间的实现等价性(参见 @ut8pia/分类器库)可以立即提供预期的顺序。repr
TrueSet
representation-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)))
而且,瞧,达到了预期的订单。
该函数对项目进行分类,分配给以数字开头的项目和其他项目。创建者中的第一个比较器对这些类进行排序,而在每个类中,顺序由第二个比较器确定。repr
1
0
ORDER.ASCENDING
TrueSet
collator.compare
需要注意的是,不会迭代重复的项目。但是,它保留了每个项目的出现次数,可通过该方法访问TrueSet
n(item)
const
got = Array.from(set)
.map(item => new Array(set.n(item)).fill(item))
.flat();
assert.deepEqual(got, expected)
评论
var ix = sortedListe.findIndex(e => /^[a-zA-Z()]+$/.test(e.charAt(0))); var result = sortedListe.slice(ix).concat(sortedListe.splice(0, ix));
['6.2', '6.10']