按数字顺序对 "advanced" 个数字字符串进行排序
Sorting "advanced" numeric strings by numeric order
想象一下这个数组:
var fees = [
'[=11=].9 + [=11=].1',
' + ',
'[=11=].7 + [=11=].4',
' + [=11=].5',
'[=11=] + [=11=].01',
'0 + ',
' + ',
' + [=11=].5'
];
我如何使用 vanilla JavaScript 以数字升序对这些字符串值进行排序?
排序后想要的输出:
['[=18=] + [=18=].01', '[=18=].7 + [=18=].4', '[=18=].9 + [=18=].1', ' + ', ' + [=18=].5', ' + [=18=].5', ' + ', '0 + ']
我尝试了以下方法:
function mySort(a, b) {
return ((a < b) ? -1 : ((a > b) ? 1 : 0));
}
但这只是输出:
["[=19=] + [=19=].01", "[=19=].7 + [=19=].4", "[=19=].9 + $.1", " + ", "0 + ", " + [=19=].5", " + ", " + [=19=].5"]`
这是否可能以简洁和合乎逻辑的方式进行?
我不想得到总和,因为它会产生不需要的结果。考虑 "[=13=].9 + [=13=].1"
和 "[=14=].7 + [=14=].4"
的例子。 "[=13=].9 + [=13=].1"
将是一个较低的值,因为总和是 1
,但我想排序以便 "[=14=].7 + [=14=].4"
是一个较低的值。所以基本上希望是对第一个数字进行升序排序,如果两个值之间的第一个数字相同,则对第二个数字进行排序
根据字符串中数字的总和排序。
var fees = ['[=10=].9 + $.1', ' + ', ' + [=10=].5', '[=10=] + [=10=].01', '0 + ', ' + ', ' + [=10=].5'];
fees.sort(function(a, b) {
return getSum(a) - getSum(b);
})
function getSum(str) {
return str
// remove the $ and space
.replace(/[^\d+.]/g, '')
//split by + symbol
.split('+')
// get the sum
.reduce(function(sum, s) {
// parse and add with sum
return sum + (Number(s) || 0);
// set inital value as sum
}, 0)
}
console.log(fees);
您可以使用保存总和的附加对象来加快该过程。
var fees = ['[=11=].9 + $.1', ' + ', ' + [=11=].5', '[=11=] + [=11=].01', '0 + ', ' + ', ' + [=11=].5'];
var ref = fees.reduce(function(obj, str) {
// define object property if not defined
obj[str] = str in obj || str
// remove the $ and space
.replace(/[^\d+.]/g, '')
//split by + symbol
.split('+')
// get the sum
.reduce(function(sum, s) {
// parse and add with sum
return sum + (Number(s) || 0);
// set inital value as sum
}, 0);
// return the object reference
return obj;
// set initial value as an empty objecct
}, {})
fees.sort(function(a, b) {
return ref[a] - ref[b];
})
console.log(fees);
更新:由于您更新了问题,因此您需要比较各个部分。
var fees = ['[=12=].9 + $.1', ' + ', ' + [=12=].5', '[=12=] + [=12=].01', '0 + ', ' + ', ' + [=12=].5'];
fees.sort(function(a, b) {
// get numbers from a
var arrA = a.replace(/[^\d.+]/g, '').split('+');
// get numbers from b
var arrB = b.replace(/[^\d.+]/g, '').split('+');
// generate sort value
return arrA[0] - arrB[0] || arrA[1] - arrB[1];
})
console.log(fees);
您可以获取字符串的各个部分,将它们相加并取两个元素的差值进行排序。
var fees = ['[=10=].9 + $.1', ' + ', ' + [=10=].5', '[=10=] + [=10=].01', '0 + ', ' + ', ' + [=10=].5'];
fees.sort(function (a, b) {
function getValues(s) {
return s.match(/([0-9.]+)/g).map(Number);
}
var aa = getValues(a),
bb = getValues(b);
return aa[0] + aa[1] - (bb[0] + bb[1]);
});
console.log(fees);
我会使用这个三步算法:
- 将要排序的两个值添加到每个元素中
- 按这些值排序
- 再次删除该额外信息
这是 ES6 代码。
var result = fees.map( s => [s].concat(s.match(/[\d.]+/g).map(Number)) )
.sort( (a, b) => a[1] - b[1] || a[2] - b[2])
.map( a => a[0] );
s.match()
调用将生成一个包含两个匹配项(字符串)的数组。 map()
调用会将它们转换为数字,concat()
会将这两个数字相加以与原始字符串形成三元组。
排序回调将按第一个数(索引为1)排序,如果相等(则差为0),将使用第二个数进行排序。
最后的 map
将从三元组中取出原始字符串,从而删除用于排序的额外信息。
在下面的代码片段中,我在您的示例数据中添加了一个元素 (.1 + [=16=].1
),这将显示与按总和排序时得到的输出的差异(您不需要,如图所示在问题的更新中)。
var fees = [
'[=11=].9 + $.1',
' + ',
' + [=11=].5',
'.1 + [=11=].1', // smaller sum than previous, but larger first value
'[=11=] + [=11=].01',
'0 + ',
' + ',
' + [=11=].5'
];
// 1. add the sum to each of the elements
// 2. sort by the sums
// 3. drop the sums
var result = fees.map( s => [s].concat(s.match(/[\d.]+/g).map(Number)) )
.sort( (a, b) => a[1] - b[1] || a[2] - b[2])
.map( a => a[0] );
console.log(result);
这种方法只会将正则表达式应用于每个字符串一次,而当您在 sort
回调中即时执行此操作时,情况并非(总是)如此。排序算法通常必须多次比较相同的值,因此在可能的情况下将逻辑移出 sort
回调可以提高性能。
想象一下这个数组:
var fees = [
'[=11=].9 + [=11=].1',
' + ',
'[=11=].7 + [=11=].4',
' + [=11=].5',
'[=11=] + [=11=].01',
'0 + ',
' + ',
' + [=11=].5'
];
我如何使用 vanilla JavaScript 以数字升序对这些字符串值进行排序?
排序后想要的输出:
['[=18=] + [=18=].01', '[=18=].7 + [=18=].4', '[=18=].9 + [=18=].1', ' + ', ' + [=18=].5', ' + [=18=].5', ' + ', '0 + ']
我尝试了以下方法:
function mySort(a, b) {
return ((a < b) ? -1 : ((a > b) ? 1 : 0));
}
但这只是输出:
["[=19=] + [=19=].01", "[=19=].7 + [=19=].4", "[=19=].9 + $.1", " + ", "0 + ", " + [=19=].5", " + ", " + [=19=].5"]`
这是否可能以简洁和合乎逻辑的方式进行?
我不想得到总和,因为它会产生不需要的结果。考虑 "[=13=].9 + [=13=].1"
和 "[=14=].7 + [=14=].4"
的例子。 "[=13=].9 + [=13=].1"
将是一个较低的值,因为总和是 1
,但我想排序以便 "[=14=].7 + [=14=].4"
是一个较低的值。所以基本上希望是对第一个数字进行升序排序,如果两个值之间的第一个数字相同,则对第二个数字进行排序
根据字符串中数字的总和排序。
var fees = ['[=10=].9 + $.1', ' + ', ' + [=10=].5', '[=10=] + [=10=].01', '0 + ', ' + ', ' + [=10=].5'];
fees.sort(function(a, b) {
return getSum(a) - getSum(b);
})
function getSum(str) {
return str
// remove the $ and space
.replace(/[^\d+.]/g, '')
//split by + symbol
.split('+')
// get the sum
.reduce(function(sum, s) {
// parse and add with sum
return sum + (Number(s) || 0);
// set inital value as sum
}, 0)
}
console.log(fees);
您可以使用保存总和的附加对象来加快该过程。
var fees = ['[=11=].9 + $.1', ' + ', ' + [=11=].5', '[=11=] + [=11=].01', '0 + ', ' + ', ' + [=11=].5'];
var ref = fees.reduce(function(obj, str) {
// define object property if not defined
obj[str] = str in obj || str
// remove the $ and space
.replace(/[^\d+.]/g, '')
//split by + symbol
.split('+')
// get the sum
.reduce(function(sum, s) {
// parse and add with sum
return sum + (Number(s) || 0);
// set inital value as sum
}, 0);
// return the object reference
return obj;
// set initial value as an empty objecct
}, {})
fees.sort(function(a, b) {
return ref[a] - ref[b];
})
console.log(fees);
更新:由于您更新了问题,因此您需要比较各个部分。
var fees = ['[=12=].9 + $.1', ' + ', ' + [=12=].5', '[=12=] + [=12=].01', '0 + ', ' + ', ' + [=12=].5'];
fees.sort(function(a, b) {
// get numbers from a
var arrA = a.replace(/[^\d.+]/g, '').split('+');
// get numbers from b
var arrB = b.replace(/[^\d.+]/g, '').split('+');
// generate sort value
return arrA[0] - arrB[0] || arrA[1] - arrB[1];
})
console.log(fees);
您可以获取字符串的各个部分,将它们相加并取两个元素的差值进行排序。
var fees = ['[=10=].9 + $.1', ' + ', ' + [=10=].5', '[=10=] + [=10=].01', '0 + ', ' + ', ' + [=10=].5'];
fees.sort(function (a, b) {
function getValues(s) {
return s.match(/([0-9.]+)/g).map(Number);
}
var aa = getValues(a),
bb = getValues(b);
return aa[0] + aa[1] - (bb[0] + bb[1]);
});
console.log(fees);
我会使用这个三步算法:
- 将要排序的两个值添加到每个元素中
- 按这些值排序
- 再次删除该额外信息
这是 ES6 代码。
var result = fees.map( s => [s].concat(s.match(/[\d.]+/g).map(Number)) )
.sort( (a, b) => a[1] - b[1] || a[2] - b[2])
.map( a => a[0] );
s.match()
调用将生成一个包含两个匹配项(字符串)的数组。 map()
调用会将它们转换为数字,concat()
会将这两个数字相加以与原始字符串形成三元组。
排序回调将按第一个数(索引为1)排序,如果相等(则差为0),将使用第二个数进行排序。
最后的 map
将从三元组中取出原始字符串,从而删除用于排序的额外信息。
在下面的代码片段中,我在您的示例数据中添加了一个元素 (.1 + [=16=].1
),这将显示与按总和排序时得到的输出的差异(您不需要,如图所示在问题的更新中)。
var fees = [
'[=11=].9 + $.1',
' + ',
' + [=11=].5',
'.1 + [=11=].1', // smaller sum than previous, but larger first value
'[=11=] + [=11=].01',
'0 + ',
' + ',
' + [=11=].5'
];
// 1. add the sum to each of the elements
// 2. sort by the sums
// 3. drop the sums
var result = fees.map( s => [s].concat(s.match(/[\d.]+/g).map(Number)) )
.sort( (a, b) => a[1] - b[1] || a[2] - b[2])
.map( a => a[0] );
console.log(result);
这种方法只会将正则表达式应用于每个字符串一次,而当您在 sort
回调中即时执行此操作时,情况并非(总是)如此。排序算法通常必须多次比较相同的值,因此在可能的情况下将逻辑移出 sort
回调可以提高性能。