2011-10-10 82 views
9

有沒有人有代碼比較JavaScript中的兩個版本號?我只是想簡單地進行版本比較(例如"1.0" vs "1.5.6"),它應該可以使用數字或字符串。它可以忽略諸如"1.5.6b4"之類的尾隨beta標識符,但可以預期字符串格式良好。該函數應該返回一個像正常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數字,但答案將得到更廣泛的應用),既然你說你

回答

14
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
  • 忽略尾隨alphabpre4
+0

正則表達式會將「1.02」轉換爲「1」,所以'cmpVersion('1.01','1.02')'給出了錯誤的結果。 –

+0

我用一種新方法編輯了這個答案的代碼,它修復了上述問題。評論將非常受歡迎,特別是要發現任何迴歸。順便說一句,這段代碼仍然不支持'cmpVersion('1.2alpha4','1.2alpha5')',但我想這會比較複雜。 –

0

如果你不關心.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")); 
+2

根據這種方法,'1.9.5 == 1.0.1'。假設OP不關心任何事情,但主要版本是一個非常大膽的假設。 – delnan

+0

我絕對關心'.5.6' – theazureshadow

4

如果你想完全正確的,看看在討論PEP386,尤其是標題爲「新版本的算法」。

否則,它看起來像你的答案是相當不錯的。

+1

+1:「無政府主義者和軟件現實主義者的版本編號。」我喜歡。我不確定我想處理所有邊緣案例,但這是一個很好的參考。謝謝! – theazureshadow

0
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)); 
} 
2

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「與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.x = >=1.2.0-0 <2.0.0-0 「1.2任何版本兼容」 := >=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
  • 1.x 「的任何1兼容版本」:= >=1.0.0-0 <2.0.0-0 「的任何版本從1開始」

範圍可與一個空間(這意味着 「和」)或 接合||(暗示「或」)。

-1

如果版本大於或等於最小版本,則此函數返回true。當版本是字符串時,假定1.0大於1。當他們是數字時,他們說他們是一樣的。如果你想讓這兩種類型返回相同的,那麼你需要將數字轉換爲字符串,這也很容易。或者你可以修改字符串條件來檢查更長的版本號是否具有1.1和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); 
} 
0

我重構雷倒要儘量精簡,我可以做到這一點。它沒有尾隨零的檢查,但可用於任意長度的構建號碼(例如major,major.minor,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中是否有數字,或者a的數字是否大於相應占位符的b的數字,在這種情況下,它將返回'小於1' 。

同樣,else語句檢查b中是否有數字,但不是a,或者b的數字是否大於a的數字,對應的位置值會返回'大於一個「。

最後一個返回0語句是一個總括性的,如果版本字符串相同,我們的函數將會得到。

0

我精心製作了下面的函數,它支持尾字母,前導零...(參看下面的例子):

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.1」之前排序「1.2a」...我認爲沒有「一個適合所有」的功能...根據您的需要選擇「更合適」功能用例,或者更好,如果可以的話,按日期排序。

更新3:修改了第一個正確處理重要情況「1.2/1.10」的函數。作爲一個副作用,它打破了不太重要的情況「1.02/1.1」,顯然它現在是唯一的警告(也許它可以修復,但我不確定它是否值得)。因此,我現在推薦固定的第一個功能。