我如何比较任意版本号

How can I compare arbitrary version numbers?

本文关键字:任意 版本号 比较 何比较      更新时间:2023-09-26

谁有代码比较两个版本号在JavaScript?我只是想要简单的版本比较(例如"1.0" vs "1.5.6"),它应该与数字或字符串一起工作。它可以忽略后面的beta标识符,如"1.5.6b4",但可以期望字符串格式良好。该函数应该像普通的cmp函数一样返回一个有符号整数。

function cmpVersion(a, b)
  return less than one if a < b
  return 0 if a == b
  return greater than one if a > b

我有一个答案,但我会选择一个比我自己的更好或更优雅的解决方案。

(我用它来比较jQuery.browser.version数字,但答案将更广泛地适用)

function cmpVersion(a, b) {
    var i, cmp, len;
    a = (a + '').split('.');
    b = (b + '').split('.');
    len = Math.max(a.length, b.length);
    for( i = 0; i < len; i++ ) {
        if( a[i] === undefined ) {
            a[i] = '0';
        }
        if( b[i] === undefined ) {
            b[i] = '0';
        }
        cmp = parseInt(a[i], 10) - parseInt(b[i], 10);
        if( cmp !== 0 ) {
            return (cmp < 0 ? -1 : 1);
        }
    }
    return 0;
}
function gteVersion(a, b) {
    return cmpVersion(a, b) >= 0;
}
function ltVersion(a, b) {
    return cmpVersion(a, b) < 0;
}

这个函数处理:

  • 数字或字符串作为输入
  • 尾随零(例如cmpVersion("1.0", 1)返回0)
  • 忽略尾随alpha, b, pre4

如果你想完全正确,看看关于PEP386的讨论,特别是标题"新的版本控制算法"。

否则看起来你的答案很好。

npm使用很好的语法来比较版本,您可以在这里获得相同的模块:https://github.com/isaacs/node-semver

支持以下范围样式:

  • 1.2.3特定版本。当什么都做不了的时候。请注意,构建元数据仍然被忽略,因此1.2.3+build2012将满足此范围。
  • >1.2.3大于指定版本。
  • <1.2.3小于特定版本。如果版本范围上没有预发布标签,则也不允许使用预发布版本,即使这些版本在技术上"小于"。
  • >=1.2.3大于等于。请注意,预发布版本不等于它们的"正常"等同物,因此1.2.3-beta将不满足此范围,但2.3.0-beta将。
  • <=1.2.3小于或等于。在这种情况下,允许预发布版本,因此1.2.3-beta将满足。
  • 1.2.3 - 2.3.4:= >=1.2.3 <=2.3.4
  • ~1.2.3:= >=1.2.3-0 <1.3.0-0 "合理接近1.2.3"。当使用波浪运算符时,也支持预发布版本,但下一个有效数字的预发布版本将不令人满意,因此1.3.0-beta将不满足~1.2.3
  • ^1.2.3:= >=1.2.3-0 <2.0.0-0 "兼容1.2.3"当使用插入符号操作符时,将支持指定版本(包括预发行版)的任何内容,直到但不包括下一个主要版本(或其预发行版)。1.5.1满足^1.2.3,而1.2.22.0.0-beta不满足。
  • ^0.1.3:= >=0.1.3-0 <0.2.0-0 "Compatible with 0.1.3"。0. x。x版本是特殊的:第一个非零组件表示潜在的破坏性更改,这意味着插入符操作符匹配从指定版本开始具有相同第一个非零组件的任何版本。
  • ^0.0.2:= =0.0.2 "只有0.0.2版本被认为是兼容的"
  • ~1.2:= >=1.2.0-0 <1.3.0-0 "任何1.2开始的版本"
  • ^1.2:= >=1.2.0-0 <2.0.0-0 "任何版本兼容1.2"
  • 1.2.x:= >=1.2.0-0 <1.3.0-0 "任何1.2开始的版本"
  • ~1:= >=1.0.0-0 <2.0.0-0 "任何以1开头的版本"
  • ^1:= >=1.0.0-0 <2.0.0-0 "Any version compatible with 1"
  • 1.x:= >=1.0.0-0 <2.0.0-0 "任何以1开头的版本"

范围可以用空格(表示"and")或||(这意味着"或")。

我编写了以下函数,它支持尾随字母,前导零…(参见下面的示例):

function cmpVersions(a, b) {
    var partsA = a.split('.');
    var partsB = b.split('.');
    var nbParts = Math.max(partsA.length, partsB.length);
    for (var i = 0; i < nbParts; ++i) {
        if (partsA[i] === undefined) {
            partsA[i] = '0';
        }
        if (partsB[i] === undefined) {
            partsB[i] = '0';
        }
        // edit: added this part
        // - fixes the important case "1.2 / 1.10"
        // - but breaks the not-so-important case "1.02 / 1.1"
        var intA = parseInt(partsA[i], 10);
        var intB = parseInt(partsB[i], 10);
        if (!isNaN(intA) && !isNaN(intB)) {
            if (intA > intB) {
                return 1;
            } else if (intA < intB) {
                return -1;
            }
        }
        var compare = partsA[i].localeCompare(partsB[i]);
        if (compare !== 0) {
            return compare;
        }
    }
    return 0;
}

那么,一些例子:

// trailing letters
cmpVersion('1.0a', '1.0b'); // -1
// leading zeroes
cmpVersion('1.01', '1.1'); // -1
// "zero" parts
cmpVersion('1', '1.0'); // 0


如果您不需要支持前导零,这里有一个更简单的替代方法:

function cmpVersions(a, b) {
    function padParts(version) {
        return version
            .split('.')
            .map(function (part) {
                return '00000000'.substr(0, 8 - part.length) + part;
            })
            .join('.');
    }
    a = padParts(a);
    b = padParts(b);
    return a.localeCompare(b);
}


快速更新:之后我注意到第一个函数在"1.10"之前排序"1.2",这是明显错误的。此外,"重要的前导零"是棘手和模糊的(无论是解释还是实现),语义版本控制显式地避免了它们。因此,我认为第二个功能应该始终是首选的。

更新2:但是第二个函数将"1.2a"排序在"1.1"之前…我认为不存在"一刀切"的功能……根据您的用例选择"更合适"的功能,或者更好,如果可以的话,不如按日期排序。

更新3:修改了第一个函数以正确处理重要的情况"1.2/1.10"。作为一个副作用,它打破了不那么重要的情况"1.02/1.1",显然这是现在唯一的警告(也许它可以修复,但我不确定它是否值得)。因此,我现在推荐固定的第一个函数。

function compareVersion(a, b) {
    return compareVersionRecursive(a.split("."), b.split("."));
}
function compareVersionRecursive(a, b) {
    if (a.length == 0) {
        a = [0];
    }
    if (b.length == 0) {
        b = [0];
    }
    if (a[0] != b[0] || (a.length == 1 && b.length == 1)) {
        return a[0] - b[0];
    }
    return compareVersionRecursive(a.slice(1), b.slice(1));
}

我已经重构了我的代码,使其尽可能简洁。它不检查末尾的零,但可以用于任何长度的构建号(例如major, major。小,major.minor.build)。

var cmpVersion = function(a, b) {
  let arrA = Array.from(a.split('.'), i => +i);
  let arrB = Array.from(b.split('.'), i => +i);
  for (let i = 0; i < (arrA.length >= arrB.length ? arrA.length : arrB.length); i++) {
    if (arrA[i] && !arrB[i] || arrA[i] > arrB[i]) return 'less than one';
    else if (!arrA[i] && arrB[i] || arrA[i] < arrB[i]) return 'greater than one';
  }
  return 0;
}

基本上,首先我从每个版本字符串中创建了一个新的数组,这样我就可以单独比较每个数字。然后在for循环中,选择最长版本字符串的长度(如果第一个版本字符串长度相等,则选择第一个版本字符串的长度)。

if语句检查a中是否有数字而b中没有,或者对于相应的占位符,a的数字是否大于b的数字,在这种情况下,它将返回'小于1 '。

同样,else语句检查b中是否有数字而a中没有,或者对于相应的位值,b中的数字是否大于a中的数字,在这种情况下,它将返回'大于1 '。

最后一个返回0语句是一个catch-all语句,如果版本字符串相等,函数将到达该语句。

如果你不关心。5.6,使用parseInt

var majorV = parseInt("1.5.6",10)

既然你说你关心次要版本:

function cmpVersion(v1, v2) {
    if(v1===v2) return 0;
    var a1 = v1.toString().split(".");
    var a2 = v2.toString().split(".");
    for( var i = 0; i < a1.length && i < a2.length; i++ ) {
        var diff = parseInt(a1[i],10) - parseInt(a2[i],10);
        if( diff>0 ) {
            return 1;
        }
        else if( diff<0 ) {
            return -1;
        }
    }
    diff = a1.length - a2.length;
    return (diff>0) ? 1 : (diff<0) ? -1 : 0;
}
console.log( cmpVersion( "1.0", "1.56") );
console.log( cmpVersion( "1.56", "1.56") );
console.log( cmpVersion( "1.65", "1.5.6") );
console.log( cmpVersion( "1.0", "1.5.6b3") );

如果版本大于或等于最小版本,则此函数返回true。当版本是字符串时,假设1.0大于1。当它们是数字时,它说它们是相同的。如果你想让这两种类型返回相同,那么你需要将数字转换为字符串,这也很容易。或者你可以修改字符串条件来检查较长的版本号是否都有末尾的零,比如1.1 vs 1.1.0.0.0.0。第二个是全以零结尾

 function doesMyVersionMeetMinimum(myVersion, minimumVersion) {
    if(typeof myVersion === 'number' && typeof minimumVersion === 'number') {
      return(myVersion >= minimumVersion);
    }
    var v1 = myVersion.split("."), v2 = minimumVersion.split("."), minLength;
    minLength= Math.min(v1.length, v2.length);
    for(i=0; i<minLength; i++) {
        if(Number(v1[i]) < Number(v2[i])) {
            return false;
        }
       else if(Number(v1[i]) < Number(v2[i])) {
            return true;
        }           
    }
    return (v1.length >= v2.length);
}