JavaScript 版本号排序实现
实现版本号数组的排序,包括字符串比较法、大数加权法和循环比较法
问题
对版本号数组进行排序,例如将 ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5'] 排序为 ['4.3.5', '4.3.4.5', '2.3.3', '0.302.1', '0.1.1']。
解答
字符串比较法
当版本号的每个子版本都是一位数字时,可以直接使用字符串比较:
const arr = ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5'];
arr.sort((a, b) => a > b ? -1 : 1);
console.log(arr); // ['4.3.5', '4.3.4.5', '2.3.3', '0.302.1', '0.1.1']
这种方法利用了字符串比较的特性:字符串比较基于 Unicode 值,逐位进行。. 的 Unicode 值为 46,0 为 48,其他数字依次递增。因此 '10.1' 大于 '1.1'。
但这种方法有局限性。如果版本号包含多位数字,比较结果会出错:
const arr = ['0.5.1', '0.302.1'];
arr.sort((a, b) => a > b ? -1 : 1);
// 结果:['0.5.1', '0.302.1'],但期望是 ['0.302.1', '0.5.1']
大数加权法
对于遵循 MAJOR.MINOR.PATCH 规则的版本号,可以使用加权计算:
const arr = ['2.3.3', '4.3.4', '0.3.1'];
const p = 1000;
const gen = (version) =>
version.split('.').reduce((acc, value, index, arr) =>
acc + (+value) * Math.pow(p, arr.length - index - 1), 0);
arr.sort((a, b) => gen(b) - gen(a));
console.log(arr);
其中 p 是常量,需要大于任何子版本号至少一个量级,避免加权后产生污染。
对于不规则长度的版本号,先获取最大长度:
const arr = ['1.1', '2.3.3', '4.3.5', '0.302.1', '4.20.0', '4.3.5.1', '1.2.3.4.5'];
const p = 1000;
const maxLen = Math.max(...arr.map(item => item.split('.').length));
const gen = (version) =>
version.split('.').reduce((acc, value, index) =>
acc + (+value) * Math.pow(p, maxLen - index - 1), 0);
arr.sort((a, b) => gen(b) - gen(a));
console.log(arr);
注意:如果版本号位数很多且值很大,可能会超出 JavaScript number 类型的精度范围,可以考虑使用 BigInt。
循环比较法
适用性最强的方法,逐位比较子版本号:
arr.sort((a, b) => {
let i = 0;
const arr1 = a.split('.');
const arr2 = b.split('.');
while (true) {
const s1 = arr1[i];
const s2 = arr2[i++];
if (s1 === undefined || s2 === undefined) {
return arr2.length - arr1.length;
}
if (s1 === s2) continue;
return s2 - s1;
}
});
console.log(arr);
这种方法的逻辑是:如果当前位相同则比较下一位,如果某个版本号已经没有更多位数,则认为位数多的版本号更大。
关键点
- 字符串比较法仅适用于所有子版本号都是一位数字的情况,因为字符串比较是基于 Unicode 值逐位进行的
- 大数加权法中的常量
p必须大于任何子版本号至少一个量级,避免加权计算时产生污染 - 循环比较法适用性最强,可以处理任意格式的版本号,通过逐位比较数值大小来判断
- 使用
split('.')将版本号字符串转换为数组后,需要将字符串转为数字进行比较 - 大数加权法在极端情况下可能溢出,未来可以使用 BigInt 解决精度问题
目录