前端知库

怎样比较两个版本号的大小?

按点号拆开,每一段转成数字从左往右比,第一段不相等就决定了结果;段数少的一方缺的部分当作 0,所以 1.0 和 1.0.0 相等。不能直接比字符串,因为字符串按字符比较,'1.10' 会比 '1.9' 小。带 -alpha 这类预发布标签时,要按 SemVer 的规则额外处理。

参考答案

简答

比如客户端要判断当前版本 1.2 是不是低于最低要求 1.10,低了就提示升级。直接用字符串比较会出错,因为字符串是一个字符一个字符比的,'2' 比 '1' 大,于是 '1.2' > '1.10'。

正确做法是按点号拆成几段,每段转成数字,从左往右比。哪一段先分出大小,结果就定了。LeetCode 165 题的规定是,段数少的一方,缺的段当作 0,每段的值忽略前导零,所以 1.01 和 1.001 相等,1.0 和 1.0.0 也相等。

返回值习惯用 -1、0、1,这样可以直接传给 sort 给一组版本号排序。

要注意,实际项目里的版本号可能带预发布标签,比如 1.0.0-alpha。按 SemVer 规范,主次修订号相同时,带预发布标签的版本更小,1.0.0-alpha < 1.0.0。简单的拆段比较处理不了这种情况,要么按规范补上,要么直接用 semver 这类库。

按段比较

下面的写法对应 LeetCode 165 的题意:段数不同时补 0,每段按整数比较。

JavaScript
function compareVersion(a, b) {
  const left = a.split('.');
  const right = b.split('.');
  const length = Math.max(left.length, right.length);

  for (let i = 0; i < length; i += 1) {
    // 缺的段按 0 算;Number 会自动忽略前导零。
    const x = Number(left[i] ?? 0);
    const y = Number(right[i] ?? 0);
    if (x > y) return 1;
    if (x < y) return -1;
  }
  return 0;
}

console.log('1.2' > '1.10'); // true
console.log(compareVersion('1.2', '1.10')); // -1
console.log(compareVersion('1.01', '1.001')); // 0
console.log(compareVersion('1.0', '1.0.0')); // 0
console.log(['1.10', '1.2', '0.9', '1.2.1'].sort(compareVersion).join(' ')); // 0.9 1.2 1.2.1 1.10

第一行就是直接比字符串的结果:'1.2' > '1.10' 是 true。比较到第三个字符时,'2' 和 '1' 比,'2' 大,后面的就不看了。拆段转成数字以后,比的是 2 和 10,结果才对。

为什么要补 0,而不是认为段多的版本更大?因为 1.0 和 1.0.0 指的是同一个版本,多出来的 .0 不该改变大小。所以循环次数取两边段数的较大值,取不到的那一段当作 0。

带预发布标签怎么办

SemVer 规定版本号是 主.次.修订,后面可以用 - 接预发布标签。比较规则是:主次修订号相同时,有预发布标签的更小;两个都有标签时,按点号拆开逐段比,纯数字的段按数字比,并且比非数字的段小。规范给的顺序是:

代码
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0

面试时能说出“先比主体,主体相同时有标签的更小”,再说明工程里用 semver 库处理,一般就够了。

常见追问

为什么不把版本号去掉点号转成一个数字再比?

因为每段位数不固定。1.10 去点号是 110,11.0 去点号也是 110,结果就分不出来了。除非先把每段补齐成固定位数,但那样还得假设每段的最大值,不如直接逐段比。

时间复杂度是多少?

拆分和比较都只扫一遍字符串,是 O(n + m),n、m 是两个版本号的长度。