[javascript] どのようにjsを使用してソフトウェアのバージョン番号を比較する? (数字のみ)



Answers

// Return 1 if a > b
// Return -1 if a < b
// Return 0 if a == b
function compare(a, b) {
    if (a === b) {
       return 0;
    }

    var a_components = a.split(".");
    var b_components = b.split(".");

    var len = Math.min(a_components.length, b_components.length);

    // loop while the components are equal
    for (var i = 0; i < len; i++) {
        // A bigger than B
        if (parseInt(a_components[i]) > parseInt(b_components[i])) {
            return 1;
        }

        // B bigger than A
        if (parseInt(a_components[i]) < parseInt(b_components[i])) {
            return -1;
        }
    }

    // If one's a prefix of the other, the longer one is greater.
    if (a_components.length > b_components.length) {
        return 1;
    }

    if (a_components.length < b_components.length) {
        return -1;
    }

    // Otherwise they are the same.
    return 0;
}

console.log(compare("1", "2"));
console.log(compare("2", "1"));

console.log(compare("1.0", "1.0"));
console.log(compare("2.0", "1.0"));
console.log(compare("1.0", "2.0"));
console.log(compare("1.0.1", "1.0"));
Question

ソフトウェアのバージョン番号は次のとおりです。

"1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"

どうすればこれを比較できますか? 正しい順序は次のとおりとします。

"1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"

アイデアは簡単です...:最初の桁を読んで、2番目の桁を3番目の桁の後に読みます。しかし、バージョン番号をフロート番号に変換することはできません....また、次のようなバージョン番号を見ることができますこの:

"1.0.0.0", "1.0.1.0", "2.0.0.0", "2.0.0.1", "2.0.1.0"

そして、これは背後にあるアイディアが何であるかを見るのがはっきりしています...しかし、それをコンピュータープログラムに変換する方法は? 誰かがこれをどのようにソートするかについての任意のアイデアを持っていますか? ありがとうございました。




I had the same problem of version comparison, but with versions possibly containing anything (ie: separators that were not dots, extensions like rc1, rc2...).

I used this, which basically split the version strings into numbers and non-numbers, and tries to compare accordingly to the type.

function versionCompare(a,b) {
  av = a.match(/([0-9]+|[^0-9]+)/g)
  bv = b.match(/([0-9]+|[^0-9]+)/g)
  for (;;) {
    ia = av.shift();
    ib = bv.shift();
    if ( (typeof ia === 'undefined') && (typeof ib === 'undefined') ) { return 0; }
    if (typeof ia === 'undefined') { ia = '' }
    if (typeof ib === 'undefined') { ib = '' }

    ian = parseInt(ia);
    ibn = parseInt(ib);
    if ( isNaN(ian) || isNaN(ibn) ) {
      // non-numeric comparison
      if (ia < ib) { return -1;}
      if (ia > ib) { return 1;}
    } else {
      if (ian < ibn) { return -1;}
      if (ian > ibn) { return 1;}
    }
  }
}

There are some assumptions here for some cases, for example : "1.01" === "1.1", or "1.8" < "1.71". It fails to manage "1.0.0-rc.1" < "1.0.0", as specified by Semantic versionning 2.0.0




バージョンをソートするためのノードモジュールを作成しました。ここで見つけることができます: version-sort

特徴

  • シーケンスの制限がありません '1.0.1.5.53.54654.114.1.154.45'
  • シーケンス長の制限なし: '1.1546515465451654654654654138754431574364321353734'
  • バージョンごとにオブジェクトをソートできます(READMEを参照)
  • ステージ(アルファ、ベータ、rc1、rc2など)

他の機能が必要な場合は、躊躇しないでください。




もし私が見たことのないリンクでこのアイデアが既に訪れていたら、私を許してください。

私は、部品を以下のような加重和に変換することでいくつかの成功を収めました:

partSum = this.major * Math.Pow(10,9);
partSum += this.minor * Math.Pow(10, 6);
partSum += this.revision * Math.Pow(10, 3);
partSum += this.build * Math.Pow(10, 0);

これは比較を非常に簡単にしました(ダブルを比較)。 バージョンフィールドは決して4桁を超えません。

7.10.2.184  -> 7010002184.0
7.11.0.1385 -> 7011001385.0

私はこれが複数の条件文が少し不合理に見えるので、誰かを助けてくれることを願っています。




このアイデアは2つのバージョンを比較しており、どれが最大のものか知っています。 "を削除します。" ベクトルの各位置を他の位置と比較します。

// Return 1  if a > b
// Return -1 if a < b
// Return 0  if a == b

function compareVersions(a_components, b_components) {

   if (a_components === b_components) {
       return 0;
   }

   var partsNumberA = a_components.split(".");
   var partsNumberB = b_components.split(".");

   for (var i = 0; i < partsNumberA.length; i++) {

      var valueA = parseInt(partsNumberA[i]);
      var valueB = parseInt(partsNumberB[i]);

      // A bigger than B
      if (valueA > valueB || isNaN(valueB)) {
         return 1;
      }

      // B bigger than A
      if (valueA < valueB) {
         return -1;
      }
   }
}



It really depends on the logic behind your versioning system . What does each number represent, and how it is used.

Is each subversion is a numeration for designating development stage? 0 for alpha 1 for beta 2 for release candidate 3 for (final) release

Is it a build version? Are you applying incremental updates?

Once you know how the versioning system works, creating the algorithm becomes easy.

If you don't allow numbers greater than 9 in each subversion, eliminating all the decimals but the first will allow you to do a straight comparison.

If you do allow numbers greater than 9 in any of subversions, there are several ways to compare them. The most obvious is to split the string by the decimals and compare each column.

But without knowing how the versioning system works, implementing a process like the ones above can get harry when version 1.0.2a is released.




私はからのバージョンが好きですが、私の見解からは、誤用の可能性があります(バージョンがsemver.org/spec/v2.0.0.htmlドキュメントと互換性があるとは思われませんが、いくつかの "ビルド番号"が使用されている場合もあります):

versionCompare( '1.09', '1.1');  // returns 1, which is wrong:  1.09 < 1.1
versionCompare('1.702', '1.8');  // returns 1, which is wrong: 1.702 < 1.8

ここで問題となるのは、バージョン番号のサブ番号が、いくつかのケースでは、数字の合理的な部分に似ている、末尾のゼロを切り捨てて(少なくとも私が最近別のソフトウェアを使用しているのを見て)書かれていることです。

5.17.2054 > 5.17.2
5.17.2 == 5.17.20 == 5.17.200 == ... 
5.17.2054 > 5.17.20
5.17.2054 > 5.17.200
5.17.2054 > 5.17.2000
5.17.2054 > 5.17.20000
5.17.2054 < 5.17.20001
5.17.2054 < 5.17.3
5.17.2054 < 5.17.30

しかしながら、第1(または第1および第2の両方)のバージョンサブ番号は、常にそれが実際に等しい整数値として扱われる。

このようなバージョン管理を使用している場合、この例では数行だけ変更することができます:

// replace this:
p1 = parseInt(v1parts[i], 10);
p2 = parseInt(v2parts[i], 10);
// with this:
p1 = i/* > 0 */ ? parseFloat('0.' + v1parts[i], 10) : parseInt(v1parts[i], 10);
p2 = i/* > 0 */ ? parseFloat('0.' + v2parts[i], 10) : parseInt(v2parts[i], 10);

したがって、最初のものを除くすべてのサブ番号は浮動小数点として比較されるので、 091はそれに応じて0.090.1になり、このように正しく比較されます。 2054および30.2054および0.3となる。

完全版は、(クレジットは ):

/** Compare two dotted version strings (like '10.2.3').
 * @returns {Integer} 0: v1 == v2, -1: v1 < v2, 1: v1 > v2
 */
function versionCompare(v1, v2) {
    var v1parts = ("" + v1).split("."),
        v2parts = ("" + v2).split("."),
        minLength = Math.min(v1parts.length, v2parts.length),
        p1, p2, i;
    // Compare tuple pair-by-pair. 
    for(i = 0; i < minLength; i++) {
        // Convert to integer if possible, because "8" > "10".
        p1 = i/* > 0 */ ? parseFloat('0.' + v1parts[i], 10) : parseInt(v1parts[i], 10);;
        p2 = i/* > 0 */ ? parseFloat('0.' + v2parts[i], 10) : parseInt(v2parts[i], 10);
        if (isNaN(p1)){ p1 = v1parts[i]; } 
        if (isNaN(p2)){ p2 = v2parts[i]; } 
        if (p1 == p2) {
            continue;
        }else if (p1 > p2) {
            return 1;
        }else if (p1 < p2) {
            return -1;
        }
        // one operand is NaN
        return NaN;
    }
    // The longer tuple is always considered 'greater'
    if (v1parts.length === v2parts.length) {
        return 0;
    }
    return (v1parts.length < v2parts.length) ? -1 : 1;
}

PSそれは遅いですが、文字列が実際に文字の配列であるという事実を操作して同じ比較関数を再使用することも考えられます:

 function cmp_ver(arr1, arr2) {
     // fill the tail of the array with smaller length with zeroes, to make both array have the same length
     while (min_arr.length < max_arr.length) {
         min_arr[min_arr.lentgh] = '0';
     }
     // compare every element in arr1 with corresponding element from arr2, 
     // but pass them into the same function, so string '2054' will act as
     // ['2','0','5','4'] and string '19', in this case, will become ['1', '9', '0', '0']
     for (i: 0 -> max_length) {
         var res = cmp_ver(arr1[i], arr2[i]);
         if (res !== 0) return res;
     }
 }



セーバー

npmによって使用されるセマンティックバージョンパーサー。

$ npm install semver

var semver = require('semver');

semver.diff('3.4.5', '4.3.7') //'major'
semver.diff('3.4.5', '3.3.7') //'minor'
semver.gte('3.4.8', '3.4.7') //true
semver.ltr('3.4.8', '3.4.7') //false

semver.valid('1.2.3') // '1.2.3'
semver.valid('a.b.c') // null
semver.clean(' =v1.2.3 ') // '1.2.3'
semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true
semver.gt('1.2.3', '9.8.7') // false
semver.lt('1.2.3', '9.8.7') // true

var versions = [ '1.2.3', '3.4.5', '1.0.2' ]
var max = versions.sort(semver.rcompare)[0]
var min = versions.sort(semver.compare)[0]
var max = semver.maxSatisfying(versions, '*')

セマンティックバージョニングリンク
https://www.npmjs.com/package/semver#prerelease-identifiers




ここに他の答えに触発されたArray.sortでの使用に適したcoffeescriptの実装があります:

# Returns > 0 if v1 > v2 and < 0 if v1 < v2 and 0 if v1 == v2
compareVersions = (v1, v2) ->
  v1Parts = v1.split('.')
  v2Parts = v2.split('.')
  minLength = Math.min(v1Parts.length, v2Parts.length)
  if minLength > 0
    for idx in [0..minLength - 1]
      diff = Number(v1Parts[idx]) - Number(v2Parts[idx])
      return diff unless diff is 0
  return v1Parts.length - v2Parts.length



Here's a version that orders version strings without allocating any sub-strings or arrays. Being that it allocates fewer objects, there's less work for the GC to do.

There is one pair of allocations (to allow reuse of the getVersionPart method), but you could expand that to avoid allocations altogether if you were very performance sensitive.

const compareVersionStrings : (a: string, b: string) => number = (a, b) =>
{
    var ia = {s:a,i:0}, ib = {s:b,i:0};
    while (true)
    {
        var na = getVersionPart(ia), nb = getVersionPart(ib);

        if (na === null && nb === null)
            return 0;
        if (na === null)
            return -1;
        if (nb === null)
            return 1;
        if (na > nb)
            return 1;
        if (na < nb)
            return -1;
    }
};

const zeroCharCode = '0'.charCodeAt(0);

const getVersionPart = (a : {s:string, i:number}) =>
{
    if (a.i >= a.s.length)
        return null;

    var n = 0;
    while (a.i < a.s.length)
    {
        if (a.s[a.i] === '.')
        {
            a.i++;
            break;
        }

        n *= 10;
        n += a.s.charCodeAt(a.i) - zeroCharCode;
        a.i++;
    }
    return n;
}



あなたはそれらを数に変換してサイズの後でソートできませんでしたか? 長さが4未満の数字に0を追加する

コンソールで遊んだ

$(["1.0.0.0", "1.0.1.0", "2.0.0.0", "2.0.0.1", "2.0.1", "3.0"]).each(function(i,e) {
    var n =   e.replace(/\./g,"");
    while(n.length < 4) n+="0" ; 
    num.push(  +n  )
});

バージョンが大きいほど、番号が大きくなります。 編集:おそらくより大きなバージョンのシリーズのアカウントに調整する必要があります




This isn't a quite solution for the question was asked, but it's very similiar.

This sort function is for semantic versions , it handles resolved version, so it doesn't work with wildcards like x or * .

It works with versions where this regular expression matches: /\d+\.\d+\.\d+.*$/ . It's very similar to this answer except that it works also with versions like 1.2.3-dev . In comparison with the other answer: I removed some checks that I don't need, but my solution can be combined with the other one.

semVerSort = function(v1, v2) {
  var v1Array = v1.split('.');
  var v2Array = v2.split('.');
  for (var i=0; i<v1Array.length; ++i) {
    var a = v1Array[i];
    var b = v2Array[i];
    var aInt = parseInt(a, 10);
    var bInt = parseInt(b, 10);
    if (aInt === bInt) {
      var aLex = a.substr((""+aInt).length);
      var bLex = b.substr((""+bInt).length);
      if (aLex === '' && bLex !== '') return 1;
      if (aLex !== '' && bLex === '') return -1;
      if (aLex !== '' && bLex !== '') return aLex > bLex ? 1 : -1;
      continue;
    } else if (aInt > bInt) {
      return 1;
    } else {
      return -1;
    }
  }
  return 0;
}

The merged solution is that:

function versionCompare(v1, v2, options) {
    var zeroExtend = options && options.zeroExtend,
        v1parts = v1.split('.'),
        v2parts = v2.split('.');

    if (zeroExtend) {
        while (v1parts.length < v2parts.length) v1parts.push("0");
        while (v2parts.length < v1parts.length) v2parts.push("0");
    }

    for (var i = 0; i < v1parts.length; ++i) {
        if (v2parts.length == i) {
            return 1;
        }
        var v1Int = parseInt(v1parts[i], 10);
        var v2Int = parseInt(v2parts[i], 10);
        if (v1Int == v2Int) {
            var v1Lex = v1parts[i].substr((""+v1Int).length);
            var v2Lex = v2parts[i].substr((""+v2Int).length);
            if (v1Lex === '' && v2Lex !== '') return 1;
            if (v1Lex !== '' && v2Lex === '') return -1;
            if (v1Lex !== '' && v2Lex !== '') return v1Lex > v2Lex ? 1 : -1;
            continue;
        }
        else if (v1Int > v2Int) {
            return 1;
        }
        else {
            return -1;
        }
    }

    if (v1parts.length != v2parts.length) {
        return -1;
    }

    return 0;
}



このブログ記事をチェックしてください。 この関数は数値バージョン番号で機能します。

function compVersions(strV1, strV2) {
  var nRes = 0
    , parts1 = strV1.split('.')
    , parts2 = strV2.split('.')
    , nLen = Math.max(parts1.length, parts2.length);

  for (var i = 0; i < nLen; i++) {
    var nP1 = (i < parts1.length) ? parseInt(parts1[i], 10) : 0
      , nP2 = (i < parts2.length) ? parseInt(parts2[i], 10) : 0;

    if (isNaN(nP1)) { nP1 = 0; }
    if (isNaN(nP2)) { nP2 = 0; }

    if (nP1 != nP2) {
      nRes = (nP1 > nP2) ? 1 : -1;
      break;
    }
  }

  return nRes;
};

compVersions('10', '10.0'); // 0
compVersions('10.1', '10.01.0'); // 0
compVersions('10.0.1', '10.0'); // 1
compVersions('10.0.1', '10.1'); // -1



One more implementation I believe worth sharing as it's short, simple and yet powerful. Please note that it uses digit comparison only. Generally it checks if version2 is later than version1 and returns true if it's the case. Suppose you have version1: 1.1.1 and version2: 1.1.2. It goes through each part of the two versions adding their parts as follows: (1 + 0.1) then (1.1 + 0.01) for version1 and (1 + 0.1) then (1.1 + 0.02) for version2.

function compareVersions(version1, version2) {

    version1 = version1.split('.');
    version2 = version2.split('.');

    var maxSubVersionLength = String(Math.max.apply(undefined, version1.concat(version2))).length;

    var reduce = function(prev, current, index) {

        return parseFloat(prev) + parseFloat('0.' + Array(index + (maxSubVersionLength - String(current).length)).join('0') + current);
    };

    return version1.reduce(reduce) < version2.reduce(reduce);
}

If you want to find the latest version out of list of versions then this might be useful:

function findLatestVersion(versions) {

    if (!(versions instanceof Array)) {
        versions = Array.prototype.slice.apply(arguments, [0]);
    }

    versions = versions.map(function(version) { return version.split('.'); });

    var maxSubVersionLength = String(Math.max.apply(undefined, Array.prototype.concat.apply([], versions))).length;

    var reduce = function(prev, current, index) {

        return parseFloat(prev) + parseFloat('0.' + Array(index + (maxSubVersionLength - String(current).length)).join('0') + current);
    };

    var sums = [];

    for (var i = 0; i < versions.length; i++) {
        sums.push(parseFloat(versions[i].reduce(reduce)));
    }

    return versions[sums.indexOf(Math.max.apply(undefined, sums))].join('.');
}

console.log(findLatestVersion('0.1000000.1', '2.0.0.10', '1.6.10', '1.4.3', '2', '2.0.0.1')); // 2.0.0.10
console.log(findLatestVersion(['0.1000000.1', '2.0.0.10', '1.6.10', '1.4.3', '2', '2.0.0.1'])); // 2.0.0.10



私がここで望んでいた機能を見つけることができませんでした。 だから私は自分自身を書いた。 これが私の貢献です。 私は誰かがそれが役に立つと願っています。

長所:

  • 任意の長さのバージョン文字列を処理します。 '1'または '1.1.1.1.1'。

  • 指定しない場合、各値は0にデフォルト設定されます。 文字列が長かったからといって、それがより大きなバージョンであるとは限りません。 ( '1'は '1.0'および '1.0.0.0'と同じにする必要があります)。

  • 数字ではない文字列を比較します。 ( '3' <'21'は真でなければなりません。

  • ループの無駄な比較に時間を費やさないでください。 (==の比較)

  • 独自のコンパレータを選択することができます。

短所:

  • バージョン文字列の文字は扱えません。 (私はそれがどのように動作するのかわかりません?)

私のコードは、 Jonによって受け入れられた答えに似ています:

function compareVersions(v1, comparator, v2) {
    "use strict";
    var comparator = comparator == '=' ? '==' : comparator;
    if(['==','===','<','<=','>','>=','!=','!=='].indexOf(comparator) == -1) {
        throw new Error('Invalid comparator. ' + comparator);
    }
    var v1parts = v1.split('.'), v2parts = v2.split('.');
    var maxLen = Math.max(v1parts.length, v2parts.length);
    var part1, part2;
    var cmp = 0;
    for(var i = 0; i < maxLen && !cmp; i++) {
        part1 = parseInt(v1parts[i], 10) || 0;
        part2 = parseInt(v2parts[i], 10) || 0;
        if(part1 < part2)
            cmp = 1;
        if(part1 > part2)
            cmp = -1;
    }
    return eval('0' + comparator + cmp);
}

compareVersions('1.2.0', '==', '1.2'); // true
compareVersions('00001', '==', '1.0.0'); // true
compareVersions('1.2.0', '<=', '1.2'); // true
compareVersions('2.2.0', '<=', '1.2'); // false



Links