[英]How to sort elements of array in natural order with mixed (letters and numbers) elements
[英]Sort Array Elements (string with numbers), natural sort
我有一个像这样的数组;
["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"]
并且需要对其进行排序,使其看起来像;
["IL0 Foo", "IL3 Bob says hello", "IL10 Baz", "PI0 Bar"]
我尝试了排序功能;
function compare(a,b) {
if (a < b)
return -1;
if (a > b)
return 1;
return 0;
}
但这给出了命令
["IL0 Foo", "IL10 Baz", "IL3 Bob says hello", "PI0 Bar"]
我试图想出一个可以工作但无法理解的正则表达式。
如果有帮助,格式将始终是 2 个字母、x 数量的数字,然后是任意数量的字符。
这称为“自然排序”,可以像这样在 JS 中实现:
function naturalCompare(a, b) { var ax = [], bx = []; a.replace(/(\\d+)|(\\D+)/g, function(_, $1, $2) { ax.push([$1 || Infinity, $2 || ""]) }); b.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; } ///////////////////////// test = [ "img12.png", "img10.png", "img2.png", "img1.png", "img101.png", "img101a.png", "abc10.jpg", "abc10", "abc2.jpg", "20.jpg", "20", "abc", "abc2", "" ]; test.sort(naturalCompare) document.write("<pre>" + JSON.stringify(test,0,3));
要以相反的顺序排序,只需交换参数:
test.sort(function(a, b) { return naturalCompare(b, a) })
或者干脆
test = test.sort(naturalCompare).reverse();
您可以将String#localeCompare
与options
灵敏度
字符串中的哪些差异应导致非零结果值。 可能的值为:
"base"
:只有基本字母不同的字符串比较为不相等。 示例:a ≠ b
,a = á
,a = A
。"accent"
:只有在基本字母或重音和其他变音符号方面不同的字符串比较为不相等。 示例:a ≠ b
,a ≠ á
,a = A
。"case"
: 只有基字母不同或大小写不同的字符串比较为不相等。 示例:a ≠ b
,a = á
,a ≠ A
。"variant"
:基本字母、重音和其他变音符号不同的字符串,或大小写比较为不相等的字符串。 也可以考虑其他差异。 示例:a ≠ b
,a ≠ á
,a ≠ A
。使用“排序”的默认值是“变体”; 它取决于使用“搜索”的语言环境。
数字
是否应使用数字排序规则,例如“1”<“2”<“10”。 可能的值为
true
和false
; 默认值为false
。 这个选项可以通过 options 属性或通过 Unicode 扩展键设置; 如果两者都提供,则options
属性优先。 不需要实现来支持此属性。
var array = ["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"]; array.sort(function (a,b) { return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }); }); console.log(array);
var re = /([a-z]+)(\d+)(.+)/i;
var arr = ["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"];
var order = arr.sort( function(a,b){
var ma = a.match(re),
mb = b.match(re),
a_str = ma[1],
b_str = mb[1],
a_num = parseInt(ma[2],10),
b_num = parseInt(mb[2],10),
a_rem = ma[3],
b_rem = mb[3];
return a_str > b_str ? 1 : a_str < b_str ? -1 : a_num > b_num ? 1 : a_num < b_num ? -1 : a_rem > b_rem;
});
我非常喜欢 georg 的解决方案,但我需要下划线(“_”)在数字之前进行排序。 这是我修改他的代码的方法:
var chunkRgx = /(_+)|([0-9]+)|([^0-9_]+)/g; function naturalCompare(a, b) { var ax = [], bx = []; a.replace(chunkRgx, function(_, $1, $2, $3) { ax.push([$1 || "0", $2 || Infinity, $3 || ""]) }); b.replace(chunkRgx, function(_, $1, $2, $3) { bx.push([$1 || "0", $2 || Infinity, $3 || ""]) }); while(ax.length && bx.length) { var an = ax.shift(); var bn = bx.shift(); var nn = an[0].localeCompare(bn[0]) || (an[1] - bn[1]) || an[2].localeCompare(bn[2]); if(nn) return nn; } return ax.length - bx.length; } ///////////////////////// test = [ "img12.png", "img10.png", "img2.png", "img1.png", "img101.png", "img101a.png", "abc10.jpg", "abc10", "abc2.jpg", "20.jpg", "20", "abc", "abc2", "_abc", "_ab_c", "_ab__c", "_abc_d", "ab_", "abc_", "_ab_cd", "" ]; test.sort(naturalCompare) document.write("<pre>" + JSON.stringify(test,0,3));
用前导零填充字符串中的数字,然后正常排序。
var naturalSort = function (a, b) { a = ('' + a).replace(/(\\d+)/g, function (n) { return ('0000' + n).slice(-5) }); b = ('' + b).replace(/(\\d+)/g, function (n) { return ('0000' + n).slice(-5) }); return a.localeCompare(b); } var naturalSortModern = function (a, b) { return ('' + a).localeCompare(('' + b), 'en', { numeric: true }); } console.dir((["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"].sort(naturalSort))); console.dir((["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"].sort(naturalSortModern)));
您可以执行这样的正则表达式来获取字符串的非数字和数字部分:
var s = "foo124bar23";
s.match(/[^\d]+|\d+/g)
返回: ["foo", "124" , "bar" , "23"]
然后在您的比较函数中,您可以遍历两个字符串的各个部分,逐个比较它们。 第一个不匹配的部分决定了整体比较的结果。 对于每个部分,检查该部分是否以数字开头,如果是,则在进行比较之前将其解析为数字。
添加另一种选择(为什么不呢):
var ary = ["IL0 Foo", "PI0 Bar", "IL10 Hello", "IL10 Baz", "IL3 Bob says hello"];
// break out the three components in to an array
// "IL10 Bar" => ['IL', 10, 'Bar']
function getParts(i){
i = i || '';
var parts = i.match(/^([a-z]+)([0-9]+)(\s.*)$/i);
if (parts){
return [
parts[1],
parseInt(parts[2], 10),
parts[3]
];
}
return []; // erroneous
}
ary.sort(function(a,b){
// grab the parts
var _a = getParts(a),
_b = getParts(b);
// trouble parsing (both fail = no shift, otherwise
// move the troubles element to end of the array)
if(_a.length == 0 && _b.length == 0) return 0;
if(_a.length == 0) return -1;
if(_b.length == 0) return 1;
// Compare letter portion
if (_a[0] < _b[0]) return -1;
if (_a[0] > _b[0]) return 1;
// letters are equal, continue...
// compare number portion
if (_a[1] < _b[1]) return -1;
if (_a[1] > _b[1]) return 1;
// numbers are equal, continue...
// compare remaining string
if (_a[2] < _b[2]) return -1;
if (_a[2] > _b[2]) return 1;
// strings are equal, continue...
// exact match
return 0;
});
不漂亮,但请检查前两个字符代码。 如果所有相等的解析和比较数字:
var arr = ["IL0 Foo", "IL10 Baz", "IL3 Bob says hello", "PI0 Bar"];
arr.sort(function (a1, b1) {
var a = parseInt(a1.match(/\d+/g)[0], 10),
b = parseInt(b1.match(/\d+/g)[0], 10),
letterA = a1.charCodeAt(0),
letterB = b1.charCodeAt(0),
letterA1 = a1.charCodeAt(1),
letterB1 = b1.charCodeAt(1);
if (letterA > letterB) {
return 1;
} else if (letterB > letterA) {
return -1;
} else {
if (letterA1 > letterB1) {
return 1;
} else if (letterB1 > letterA1) {
return -1;
}
if (a < b) return -1;
if (a > b) return 1;
return 0;
}
});
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.