Як порівняти номер версії програмного забезпечення за допомогою js? (лише номер)


164

Ось номер версії програмного забезпечення:

"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"

Ідея проста ...: Прочитайте першу цифру, ніж, другу, після цього третю .... Але я не можу перетворити номер версії в плаваючий номер .... Ви також можете побачити номер версії, як це:

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

і це зрозуміліше, щоб побачити, що стоїть за ідеєю ... Але, як перетворити її в комп'ютерну програму ?? У когось є ідеї про те, як це сортувати? Дякую.


5
Це було б гарним питанням інтерв'ю типу fizzbuzz.
Стів Клардж

2
Ось чому всі номери версій програмного забезпечення повинні бути цілими числами, як 2001403. Коли ви хочете відобразити його таким дружним способом, як "2.0.14.3", тоді ви форматуєте номер версії на час презентації.
jarmod

2
Загальна проблема тут - порівняння семантичної версії, і це нетривіально (див. №11 на semver.org ). На щастя, для цього є офіційна бібліотека, семантична версія для npm .
Дан Даскалеску

1
Знайшов простий сценарій, який порівнює semvers
vsync

Відповіді:


133

Основною ідеєю для цього порівняння було б використання Array.split для отримання масивів частин із вхідних рядків, а потім порівнювати пари частин з двох масивів; якщо деталі не рівні, ми знаємо, яка версія менша.

Майте на увазі кілька важливих деталей:

  1. Як слід порівнювати частини кожної пари? Питання хоче порівняти чисельно, але що робити, якщо у нас є рядки версій, які не складаються з просто цифр (наприклад, "1.0a")?
  2. Що має статися, якщо одна версія версії має більше частин, ніж інша? Швидше за все, "1.0" слід вважати меншим, ніж "1.0.1", а як щодо "1.0.0"?

Ось код для реалізації, який ви можете використовувати безпосередньо ( суть із документацією ):

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

    function isValidPart(x) {
        return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
    }

    if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
        return NaN;
    }

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

    if (!lexicographical) {
        v1parts = v1parts.map(Number);
        v2parts = v2parts.map(Number);
    }

    for (var i = 0; i < v1parts.length; ++i) {
        if (v2parts.length == i) {
            return 1;
        }

        if (v1parts[i] == v2parts[i]) {
            continue;
        }
        else if (v1parts[i] > v2parts[i]) {
            return 1;
        }
        else {
            return -1;
        }
    }

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

    return 0;
}

Ця версія порівнює частини природним шляхом , не приймає символи суфіксів і вважає, що "1.7" є меншим за "1.7.0". Режим порівняння може бути змінений на лексикографічний, а рядки коротшої версії можуть бути автоматично нульовані, використовуючи додатковий третій аргумент.

Є JSFiddle, який виконує "одиничні тести" тут ; це трохи розширена версія роботи ripper234 (дякую).

Важлива примітка: Цей код використовує Array.mapі Array.every, що означає, що він не буде працювати у версіях IE раніше, ніж 9. Якщо вам потрібно підтримати ці, вам доведеться надати поліфауни для відсутніх методів.


16
Ось покращена версія з деякими тестовими одиницями
ripper234

5
Привіт усім, я перекотив цю суть у gitrepo з тестами і всім, і поставив її на npm та bower, щоб я міг легше включити її до своїх проектів. github.com/gabe0x02/version_compare
Габріель

2
@GabrielLittman: Ей, дякую, що знайшли час для цього! Однак весь код на SO ліцензований за допомогою CC-BY-SA за замовчуванням. Це означає, що ви не можете мати ліцензію на отримання GPL. Я знаю, що правознавство - це не те, для кого тут є, але було б добре, якби ви виправили це.
Іван

2
@GabrielLittman: GPL насправді дуже обмежує те, що ви змушені GPL-ліцензувати весь код, який контактує з існуючим кодом GPL. У будь-якому випадку для подальшої довідки: хороша і широко застосовувана ліцензія "роби все, що хочеш, жодних рядків не додається" - це MIT .
Іван


82

semver

Семантичний аналізатор версій, що використовується npm.

$ npm встановити 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


8
Так. Це правильна відповідь - порівняння версій нетривіальне (див. №11 на semver.org ), і є бібліотеки рівня виробництва, які виконують цю роботу.
Дан Даскалеску

7
технічно це не правильні відповіді, оскільки node.js та javascript відрізняються. Я вважав, що оригінальне запитання було націлене більше на браузер. Але google привів мене сюди, і на щастя, я використовую вузол :)
Lee Gary

2
NodeJS - це не лише серверне рішення. Electron Framework вбудовує вузол JS для настільних додатків. Це насправді відповідь, яку я шукав.
Ентоні Реймонд

2
semver - це пакет npm, його можна використовувати в будь-якому середовищі JS! Це правильна відповідь
neiker

4
@artuska добре, тоді просто перейдіть на інший пакет, як semver-порівняння - 233B (менше 0,5 кБ!) gzipped
:)

50
// 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"));

Я думаю, що рядок: var len = Math.min(a_components.length, b_components.length);змусить версії 2.0.1.1 та 2.0.1 трактуватись як рівні?
Джон Егертон

1
Ні. Дивіться відразу після циклу! Якщо один рядок є префіксом іншого (тобто цикл доходить до кінця), то довший вважається вищим.
Джо

Можливо, ви відклали моє спотикання над англійською мовою в коментарі ...
Джо

@Joe Я знаю, це трохи стара відповідь, але я використовував цю функцію. Тестування a = '7'та b = '7.0'повернення, -1оскільки 7.0 довше. Є якісь пропозиції щодо цього? ( console.log(compare("7", "7.0")); //returns -1)
РафаельDDL

Я припускаю, що це під заголовком невизначеної поведінки. Якщо у вас є ці номери версій, я впевнений, що ви можете змінити логіку, щоб відповідати вашим вимогам.
Джо,

48

Ця дуже маленька, але дуже швидка функція порівняння приймає номери версій будь-якої довжини та будь-якого розміру чисельності на сегмент .

Повернені значення:
- число, < 0якщо a <b
- число, > 0якщо a> b
- 0якщо a = b

Таким чином, ви можете використовувати його як функцію порівняння для Array.sort ();

РЕДАКТУВАННЯ : Виправлена ​​помилка версії позбавлення нульових знаків, щоб визнати "1" та "1.0.0" рівними

function cmpVersions (a, b) {
    var i, diff;
    var regExStrip0 = /(\.0+)+$/;
    var segmentsA = a.replace(regExStrip0, '').split('.');
    var segmentsB = b.replace(regExStrip0, '').split('.');
    var l = Math.min(segmentsA.length, segmentsB.length);

    for (i = 0; i < l; i++) {
        diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
        if (diff) {
            return diff;
        }
    }
    return segmentsA.length - segmentsB.length;
}

// TEST
console.log(
['2.5.10.4159',
 '1.0.0',
 '0.5',
 '0.4.1',
 '1',
 '1.1',
 '0.0.0',
 '2.5.0',
 '2',
 '0.0',
 '2.5.10',
 '10.5',
 '1.25.4',
 '1.2.15'].sort(cmpVersions));
// Result:
// ["0.0.0", "0.0", "0.4.1", "0.5", "1.0.0", "1", "1.1", "1.2.15", "1.25.4", "2", "2.5.0", "2.5.10", "2.5.10.4159", "10.5"]


Помилка з "0.0" та "0.0.0". Дивіться скрипку: jsfiddle.net/emragins/9e9pweqg
emragins

1
@emragins Коли вам це потрібно зробити?
Скайлар Іттнер

1
@emragins: Я не бачу, де це виходить з ладу. Він виводить ["0.0.0", "0.0", "0.4.1", "0.5", "1.0.0", "1", "1.1", "1.2.15", "1.25.4", "2", "2.5.0", "2.5.10", "2.5.10.4159", "10.5"] там, де виводиться ваш код ["0.0", "0.0.0", "0.4.1", "0.5", "1", "1.0.0", "1.1", "1.2.15", "1.25.4", "2", "2.5.0", "2.5.10", "2.5.10.4159", "10.5"] , який абсолютно однаковий, оскільки 0,0 та 0,0,0 вважаються рівними , це означає, що не має значення, чи є "0,0" перед "0,0,0" або навпаки.
LeJared

Я згоден, це звичайний момент. Я використовую це за допомогою github.com/jonmiles/bootstrap-treeview , який визначає рівні вузлів подібним до версій, тільки це насправді лише батьківські / дочірні вузли та їх індекси. Вих. Батько: 0,0, дитина: 0,0,0, 0,0,1. Дивіться цю проблему, щоб отримати докладнішу інформацію про те, чому я хвилююсь
emragins

1
Дивіться відповідь тут stackoverflow.com/questions/6611824/why-do-we-need-to-use-radix . Старі браузери, які використовуються для відгадування параметра radix, якщо не вказані. Провідний нуль у рядку чисел, як середня частина у "1.09.12", був використаний для розбору radix = 8, у результаті чого число 0 замість очікуваного числа 9.
LeJared

14

Взято з http://java.com/js/deployJava.js :

    // return true if 'installed' (considered as a JRE version string) is
    // greater than or equal to 'required' (again, a JRE version string).
    compareVersions: function (installed, required) {

        var a = installed.split('.');
        var b = required.split('.');

        for (var i = 0; i < a.length; ++i) {
            a[i] = Number(a[i]);
        }
        for (var i = 0; i < b.length; ++i) {
            b[i] = Number(b[i]);
        }
        if (a.length == 2) {
            a[2] = 0;
        }

        if (a[0] > b[0]) return true;
        if (a[0] < b[0]) return false;

        if (a[1] > b[1]) return true;
        if (a[1] < b[1]) return false;

        if (a[2] > b[2]) return true;
        if (a[2] < b[2]) return false;

        return true;
    }

Просте, але обмежене трьома версіями поля.
Дан Даскалеску

11

Не вдалося знайти функцію, що робить те, що я хотів тут. Тому я написав своє. Це мій внесок. Сподіваюся, хтось вважає це корисним.

Плюси:

  • Обробляє рядки версії довільної довжини. '1' або '1.1.1.1.1'.

  • За замовчуванням кожне значення до 0, якщо воно не вказано. Тільки тому, що рядок довший, не означає, що це більша версія. ("1" має відповідати "1.0" та "1.0.0.0".)

  • Порівняйте числа, а не рядки. ('3' <'21' має бути правдивим. Неправдивим.)

  • Не витрачайте час на марні порівняння в циклі. (Порівнюючи для ==)

  • Ви можете вибрати власний компаратор.

Мінуси:

  • Він не обробляє літери в рядку версії. (Я не знаю, як це могло б працювати навіть?)

Мій код, подібний до прийнятої відповіді Джона :

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

ця версія є, на мою думку, кращою, ніж версія у схваленій відповіді!
користувач3807877

1
Ця функція схильна до введення коду, якщо параметр компаратора використовується з неперевіреним введенням користувача! Приклад: порівнятиVersions ('1.2', '== 0; alert ("cotcha");', '1.2');
LeJared

@LeJared True. Коли я написав це, ми не збиралися використовувати його з представленим користувачем кодом. Мабуть, це вигадав як шахрай. Зараз я оновив код, щоб усунути таку можливість. Хоча, коли webpack та інші постачальники node.js стали поширеними, я б припустив, що відповідь Мухаммеда Акдіма вище, використовуючи semver, майже завжди буде правильною відповіддю на це питання.
Віктор

10

Проста і коротка функція:

function isNewerVersion (oldVer, newVer) {
  const oldParts = oldVer.split('.')
  const newParts = newVer.split('.')
  for (var i = 0; i < newParts.length; i++) {
    const a = parseInt(newParts[i]) || 0
    const b = parseInt(oldParts[i]) || 0
    if (a > b) return true
    if (a < b) return false
  }
  return false
}

Тести:

isNewerVersion('1.0', '2.0') // true
isNewerVersion('1.0', '1.0.1') // true
isNewerVersion('1.0.1', '1.0.10') // true
isNewerVersion('1.0.1', '1.0.1') // false
isNewerVersion('2.0', '1.0') // false
isNewerVersion('2', '1.0') // false
isNewerVersion('2.0.0.0.0.1', '2.1') // true
isNewerVersion('2.0.0.0.0.1', '2.0') // false

Ви можете спростити це за допомогою: const a = ~~ newParts [i]; Насправді це найефективніший спосіб перетворення рядка в ціле число, яке повертає 0, якщо змінна не визначена або містить нечислові символи.
vanowm

5

Пробачте мене, якщо цю ідею вже відвідали за посиланням, якого я не бачив.

Я мав певний успіх у перетворенні деталей на зважену суму так:

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
Це порушиться, якщо this.minor> 999 (буде перетинатися з майором)
Афанасій Куракін

5

Ось ще одна коротка версія, яка працює з будь-якою кількістю під версій, підписаних нулями та парними цифрами з літерами (1.0.0b3)

function compareVer(a, b)
{
    //treat non-numerical characters as lower version
    //replacing them with a negative number based on charcode of each character
    function fix(s)
    {
        return "." + (s.toLowerCase().charCodeAt(0) - 2147483647) + ".";
    }
    a = ("" + a).replace(/[^0-9\.]/g, fix).split('.');
    b = ("" + b).replace(/[^0-9\.]/g, fix).split('.');
    var c = Math.max(a.length, b.length);
    for (var i = 0; i < c; i++)
    {
        //convert to integer the most efficient way
        a[i] = ~~a[i];
        b[i] = ~~b[i];
        if (a[i] > b[i])
            return 1;
        else if (a[i] < b[i])
            return -1;
    }
    return 0;
}

Вихід:

0 : a = b

1 : a> b

-1 : а <b

1.0.0.0.0.0 = 1.0
1.0         < 1.0.1
1.0b1       < 1.0
1.0a        < 1.0b
1.1         > 1.0.1b
1.1alpha    < 1.1beta
1.1rc1      > 1.1beta
1.0001      > 1.00000.1.0.0.0.01

https://jsfiddle.net/vanowm/p7uvtbor/


5

Відповідь 2017 року:

v1 = '20.0.12'; 
v2 = '3.123.12';

compareVersions(v1,v2) 
// return positive: v1 > v2, zero:v1 == v2, negative: v1 < v2 
function compareVersions(v1, v2) {
        v1= v1.split('.')
        v2= v2.split('.')
        var len = Math.max(v1.length,v2.length)
        /*default is true*/
        for( let i=0; i < len; i++)
            v1 = Number(v1[i] || 0);
            v2 = Number(v2[i] || 0);
            if (v1 !== v2) return v1 - v2 ;
            i++;
        }
        return 0;
    }

Найпростіший код для сучасних браузерів:

 function compareVersion2(ver1, ver2) {
      ver1 = ver1.split('.').map( s => s.padStart(10) ).join('.');
      ver2 = ver2.split('.').map( s => s.padStart(10) ).join('.');
      return ver1 <= ver2;
 }

Ідея тут полягає в порівнянні чисел, але у вигляді рядка. щоб зробити порівняльну роботу, два рядки повинні бути однакової довжини. так:

"123" > "99" стати "123" > "099"
прокладкою коротке число "виправити" порівняння

Тут я пробиваю кожну частину нулями довжиною до 10. Тоді просто використовую для порівняння простий рядок

Приклад:

var ver1 = '0.2.10', ver2=`0.10.2`
//become 
ver1 = '0000000000.0000000002.0000000010'
ver2 = '0000000000.0000000010.0000000002'
// then it easy to see that
ver1 <= ver2 // true

Ви б пояснили функцію, compareVersion2що саме відбувається?
Усман Валі

Добре, тоді ви можете використовувати substringзамість padStartкращої сумісності, тобто var zeros = "0000000000"; '0.2.32'.split('.').map( s => zeros.substring(0, zeros.length-s.length) + s ).join('.') дасть вам 0000000000.0000000002.0000000032:)
Usman Wali


4

Моя менш докладна відповідь, ніж більшість відповідей тут

/**
 * Compare two semver versions. Returns true if version A is greater than
 * version B
 * @param {string} versionA
 * @param {string} versionB
 * @returns {boolean}
 */
export const semverGreaterThan = function(versionA, versionB){
  var versionsA = versionA.split(/\./g),
    versionsB = versionB.split(/\./g)
  while (versionsA.length || versionsB.length) {
    var a = Number(versionsA.shift()), b = Number(versionsB.shift())
    if (a == b)
      continue
    return (a > b || isNaN(b))
  }
  return false
}

1
ви повинні зробити його модулем і розмістити його на node.js. до цього часу я краду ваш код із віднесенням до вас. дякую за це
r3wt

3

Хоча цього питання вже багато відповідей, кожен з них пропонує своє власне рішення на задньому дворі, тоді як у нас є ціла екосистема (битви) перевірених бібліотек для цього.

Швидкий пошук по NPM , GitHub , X дасть нам чудові лійки, і я хотів би пройти кілька:

semver-compareце відмінний легкий (~ 230B) Lib , що особливо корисно , якщо ви хочете впорядкувати за номерами версій, як відкриті метод повертає бібліотеки -1, 0або 1відповідним чином .

Серцевина кореля:

module.exports = function cmp (a, b) {
    var pa = a.split('.');
    var pb = b.split('.');
    for (var i = 0; i < 3; i++) {
        var na = Number(pa[i]);
        var nb = Number(pb[i]);
        if (na > nb) return 1;
        if (nb > na) return -1;
        if (!isNaN(na) && isNaN(nb)) return 1;
        if (isNaN(na) && !isNaN(nb)) return -1;
    }
    return 0;
};

compare-semver має досить здоровенний розмір (~ 4,4 кбіт gzipped), але дозволяє здійснити непогані унікальні порівняння, як знайти мінімум / макс стеку версій або дізнатись, чи надана версія унікальна чи менше, ніж будь-що інше у колекції версії.

compare-versionsце ще одна невелика вкладка (~ 630B gzipped) і добре відповідає технічним характеристикам, тобто ви можете порівнювати версії з альфа-бета-прапорами та навіть з маскими символами (наприклад, для мінорних / патч-версій: 1.0.xабо1.0.* )

Справа в тому, що не завжди потрібно копіювати і вставляти код із StackOverflow, якщо ви можете знайти пристойні, (одиничні) перевірені версії через свій вибір менеджера пакунків.


3

Я зіткнувся з подібним питанням, і вже створив його рішення. Сміливо спробуйте.

Він повертається 0за equal, 1якщо версія є, greaterі -1якщо вона єless

function compareVersion(currentVersion, minVersion) {
  let current = currentVersion.replace(/\./g," .").split(' ').map(x=>parseFloat(x,10))
  let min = minVersion.replace(/\./g," .").split(' ').map(x=>parseFloat(x,10))

  for(let i = 0; i < Math.max(current.length, min.length); i++) {
    if((current[i] || 0) < (min[i] || 0)) {
      return -1
    } else if ((current[i] || 0) > (min[i] || 0)) {
      return 1
    }
  }
  return 0
}


console.log(compareVersion("81.0.1212.121","80.4.1121.121"));
console.log(compareVersion("81.0.1212.121","80.4.9921.121"));
console.log(compareVersion("80.0.1212.121","80.4.9921.121"));
console.log(compareVersion("4.4.0","4.4.1"));
console.log(compareVersion("5.24","5.2"));
console.log(compareVersion("4.1","4.1.2"));
console.log(compareVersion("4.1.2","4.1"));
console.log(compareVersion("4.4.4.4","4.4.4.4.4"));
console.log(compareVersion("4.4.4.4.4.4","4.4.4.4.4"));
console.log(compareVersion("0","1"));
console.log(compareVersion("1","1"));
console.log(compareVersion("1","1.0.00000.0000"));
console.log(compareVersion("","1"));
console.log(compareVersion("10.0.1","10.1"));


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;
      }
   }
}

Епічна відповідь, саме те, що я шукав.
Вінс

2
// Returns true if v1 is bigger than v2, and false if otherwise.
function isNewerThan(v1, v2) {
      v1=v1.split('.');
      v2=v2.split('.');
      for(var i = 0; i<Math.max(v1.length,v2.length); i++){
        if(v1[i] == undefined) return false; // If there is no digit, v2 is automatically bigger
        if(v2[i] == undefined) return true; // if there is no digit, v1 is automatically bigger
        if(v1[i] > v2[i]) return true;
        if(v1[i] < v2[i]) return false;
      }
      return false; // Returns false if they are equal
    }

1
Ласкаво просимо до SO. На це запитання вже є багато хороших відповідей. Утримайтеся від додавання нових відповідей, якщо ви не додасте щось нове.
до

1

replace()Функція замінює тільки перше входження в рядку. Таким чином, дозволяє замінити .з ,. Після цього видалити всі .і зробити , ,щоб .знову і розібрати його , щоб плавати.

for(i=0; i<versions.length; i++) {
    v = versions[i].replace('.', ',');
    v = v.replace(/\./g, '');
    versions[i] = parseFloat(v.replace(',', '.'));
}

нарешті, сортуйте:

versions.sort();

1

Перегляньте цю публікацію в блозі . Ця функція працює для числових номерів версій.

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

1

Якщо, наприклад, ми хочемо перевірити, чи поточна версія jQuery менша, ніж 1,8, parseFloat($.ui.version) < 1.8 )дала б неправильний результат, якщо версія "1.10.1", оскільки parseFloat ("1.10.1") повертається 1.1. Порівняння рядків також піде не так, оскільки "1.8" < "1.10"оцінюється доfalse .

Тож нам потрібен такий тест

if(versionCompare($.ui.version, "1.8") < 0){
    alert("please update jQuery");
}

Наступна функція справляється з цим правильно:

/** 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 = parseInt(v1parts[i], 10);
        p2 = 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;
}

Ось кілька прикладів:

// compare dotted version strings
console.assert(versionCompare("1.8",      "1.8.1")    <   0);
console.assert(versionCompare("1.8.3",    "1.8.1")    >   0);
console.assert(versionCompare("1.8",      "1.10")     <   0);
console.assert(versionCompare("1.10.1",   "1.10.1")   === 0);
// Longer is considered 'greater'
console.assert(versionCompare("1.10.1.0", "1.10.1")   >   0);
console.assert(versionCompare("1.10.1",   "1.10.1.0") <   0);
// Strings pairs are accepted
console.assert(versionCompare("1.x",      "1.x")      === 0);
// Mixed int/string pairs return NaN
console.assert(isNaN(versionCompare("1.8", "1.x")));
//works with plain numbers
console.assert(versionCompare("4", 3)   >   0);

Ознайомтеся з сюжетом живого зразка та тестового набору: http://jsfiddle.net/mar10/8KjvP/


arghh, щойно помітив, що ripper234 опублікував скрипт-URL у своїх коментарях кілька місяців тому, що досить схоже. У всякому разі, я дотримуюся своєї відповіді тут ...
mar10,

Цей варіант також не вдасться (як і більшість варіантів навколо) у цих випадках: versionCompare ('1,09', '1,1') повертає "1", так само, як і версіяCompare ('1.702', '1.8').
shaman.sir

Код оцінює "1,09"> "1,1" та "1,770"> "1,8", що я вважаю правильним. Якщо ви не згодні: чи можете ви вказати на якийсь ресурс, який підтримує вашу думку?
mar10,

Це залежить від ваших принципів - наскільки я знаю, немає жорсткого правила чи чогось іншого. Щодо ресурсів, стаття Вікіпедії для "Версії програмного забезпечення" у "Збільшенні послідовностей" говорить, що 1,81 може бути другорядною версією 1,8, тому 1,8 має читати як 1,80. Стаття про семантичну версію semver.org/spec/v2.0.0.html також говорить, що 1.9.0 -> 1.10.0 -> 1.11.0, тому 1.9.0 трактується як 1.90.0 у порівнянні з цим. Отже, дотримуючись цієї логіки, версія 1.702 була до версії 1.8, яка трактується як 1.800.
shaman.sir

1
Я бачу, що деякі правила стосуються 1,8 <1,81 <1,9. Але в semver ви б використовували 1.8.1 замість 1.81. Semver (наскільки я це розумію) визначається навколо припущення, що збільшення частини завжди генерує "пізнішу" версію, тому 1,8 <1,8,1 <1,9 <1,10 <1,81 <1,90 <1,100. Я не бачу вказівки, що це обмежене двома цифрами. Тому я б сказав, що мій код повністю відповідає semver.
mar10,

1

Ось реалізація кофескрипту, придатна для використання з Array.sort, натхненна іншими відповідями тут:

# 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

На це надихає відповідь Лежаред .
Дан Даскалеску

це не працює належним чином ... ось результат .. результат ['1.1.1', '2.1.1', '3.3.1.0', '3.1.1.0']
ertan2002

1

Я написав модуль вузла для сортування версій, ви можете знайти його тут: сортування-версія

Особливості :

  • без обмежень послідовностей працює «1.0.1.5.53.54654.114.1.154.45»
  • без обмеження довжини послідовності: '1.1546515465451654654654654138754431574364321353734' працює
  • може сортувати об'єкти за версією (див. README)
  • етапи (наприклад, альфа, бета, rc1, rc2)

Не соромтеся відкривати проблему, якщо вам потрібна інша функція.


1

Це працює для числових версій будь-якої довжини, розділених періодом. Він повертає істину лише у тому випадку, якщо myVersion> = minimalVersion, припускаючи, що версія 1 менше 1,0, версія 1.1 менше 1,1,0 тощо. Додавати додаткові умови, такі як прийняття чисел (просто перетворення у рядок), і шістнадцятковий або створення розділочника динамічним (просто додати параметр відмежувача, потім замінити параметр "." На параметр).

function versionCompare(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 true;
        }
        if(Number(v1[i]) < Number(v2[i])) {
            return false;
        }           
    }

    return (v1.length >= v2.length);
}

Ось кілька тестів:

console.log(versionCompare("4.4.0","4.4.1"));
console.log(versionCompare("5.24","5.2"));
console.log(versionCompare("4.1","4.1.2"));
console.log(versionCompare("4.1.2","4.1"));
console.log(versionCompare("4.4.4.4","4.4.4.4.4"));
console.log(versionCompare("4.4.4.4.4.4","4.4.4.4.4"));
console.log(versionCompare("0","1"));
console.log(versionCompare("1","1"));
console.log(versionCompare("","1"));
console.log(versionCompare("10.0.1","10.1"));

Крім того, тут є рекурсивна версія

function versionCompare(myVersion, minimumVersion) {
  return recursiveCompare(myVersion.split("."),minimumVersion.split("."),Math.min(myVersion.length, minimumVersion.length),0);
}

function recursiveCompare(v1, v2,minLength, index) {
  if(Number(v1[index]) < Number(v2[index])) {
    return false;
  }
  if(Number(v1[i]) < Number(v2[i])) {
    return true;
    }
  if(index === minLength) {
    return (v1.length >= v2.length);
  }
  return recursiveCompare(v1,v2,minLength,index+1);
}

1

Я знаходжу найпростіший спосіб порівняти їх, не впевнений, чи це те, що ти хочеш. коли я запускаю нижче коду в консолі, це має сенс, і використовуючи метод sort (), я міг отримати відсортований масив рядків версій. вона заснована на алфавітному порядку.

"1.0" < "1.0.1" //true
var arr = ["1.0.1", "1.0", "3.2.0", "1.3"]
arr.sort();     //["1.0", "1.0.1", "1.3", "3.2.0"]

3
Це не добре працює для двозначних номерів версій, наприклад, 1.10.0.
Leukipp

1

Ви можете використовувати String#localeCompareзoptions

чутливість

Які різниці в рядках повинні призвести до ненульових значень результату. Можливі значення:

  • "base": Тільки рядки, які відрізняються основними літерами, порівнюються як неоднакові. Приклади: a ≠ b, a = á, a = A.
  • "accent": Тільки рядки, які відрізняються основними літерами або наголосами та іншими діакритичними позначками, порівнюються як нерівні. Приклади: a ≠ b, a ≠ á, a = A.
  • "case": Тільки рядки, які відрізняються основними літерами або великими літерами, порівнюються як нерівні. Приклади: a ≠ b, a = á, a ≠ A.
  • "variant": Рядки, які відрізняються основними літерами, наголосами та іншими діакритичними позначками або регістром, порівнюються як нерівні. Інші відмінності також можуть бути враховані. Приклади: a ≠ b, a ≠ á, a ≠ A.

За замовчуванням є "варіант" для використання "сортування"; це локальна залежність для використання "пошук".

числовий

Чи слід застосовувати числове порівняння таким чином, щоб "1" <"2" <"10". Можливі значення є trueі false; за замовчуванням - false. Цей параметр можна встановити через властивість параметрів або через розширення Unicode; якщо надано обидва, optionsвластивість має перевагу. Для підтримки цього ресурсу не потрібно реалізовувати.

var versions = ["2.0.1", "2.0", "1.0", "1.0.1", "2.0.0.1"];

versions.sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }));

console.log(versions);


Як це насправді працює? Що undefinedвище, мова? Як же вам вдається опублікувати це, поки я читаю інших;)
mplungjan

undefinedце частина локалів, тут не використовується.
Ніна Шольц

0

Ви не могли перетворити їх у числа, а потім сортувати за розміром? Додайте 0 до числа до чисел, що мають довжину <4

грали в консолі:

$(["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  )
});

чим більша версія, тим більша кількість. Редагувати: ймовірно, потрібне коригування для врахування більшої серії версій


Це був лише приклад, оскільки він повинен робити деякі речі сам: P Замість 4 отримайте кількість чисел, які має найбільша версія, а потім заповніть ті, які є нижчими за 0,
Контракт

0

Це акуратний трюк. Якщо ви маєте справу з числовими значеннями, між певним діапазоном значень ви можете призначити значення кожному рівню об’єкта версії. Наприклад, "найбільшаValue" тут встановлена ​​на 0xFF, що створює дуже "IP" вигляд вашої версії.

Це також обробляє альфа-числові версії (тобто 1.2a <1.2b)

// The version compare function
function compareVersion(data0, data1, levels) {
    function getVersionHash(version) {
        var value = 0;
        version = version.split(".").map(function (a) {
            var n = parseInt(a);
            var letter = a.replace(n, "");
            if (letter) {
                return n + letter[0].charCodeAt() / 0xFF;
            } else {
                return n;
            }
        });
        for (var i = 0; i < version.length; ++i) {
            if (levels === i) break;
            value += version[i] / 0xFF * Math.pow(0xFF, levels - i + 1);
        }
        return value;
    };
    var v1 = getVersionHash(data0);
    var v2 = getVersionHash(data1);
    return v1 === v2 ? -1 : v1 > v2 ? 0 : 1;
};
// Returns 0 or 1, correlating to input A and input B
// Direct match returns -1
var version = compareVersion("1.254.253", "1.254.253a", 3);

0

Мені подобається версія від @ mar10 , хоча, з моєї точки зору, є ймовірність неправомірного використання (здається, це не так, якщо версії сумісні з документом Semantic Versioning , але це може бути так, якщо використовується якийсь "номер збірки" ):

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

Перший (або і перший, і другий) під-номер версії, однак, завжди трактується як ціле значення, якому воно фактично дорівнює.

Якщо ви використовуєте такий варіант версій, ви можете змінити лише кілька рядків у прикладі:

// 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);

Таким чином , кожен суб-номер , крім першого буде порівнюватися як поплавок, так 09і 1буде , 0.09і , 0.1відповідно , і по порівнянні належним чином таким чином. 2054і 3стане 0.2054і0.3 .

Повна версія тоді (кредити до @ mar10 ):

/** 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;
     }
 }

0

Я зробив це на основі ідеї Kons і оптимізував її для версії Java "1.7.0_45". Це лише функція, призначена для перетворення рядка версії в float. Це функція:

function parseVersionFloat(versionString) {
    var versionArray = ("" + versionString)
            .replace("_", ".")
            .replace(/[^0-9.]/g, "")
            .split("."),
        sum = 0;
    for (var i = 0; i < versionArray.length; ++i) {
        sum += Number(versionArray[i]) / Math.pow(10, i * 3);
    }
    console.log(versionString + " -> " + sum);
    return sum;
}

Рядок "1.7.0_45" перетворюється на 1.0070000450000001, і це досить добре для нормального порівняння. Тут пояснюється помилка: як боротися з точністю числа з плаваючою комою в JavaScript? . Якщо вам потрібно більше 3-х цифр на будь-якій частині, ви можете змінити дільник Math.pow(10, i * 3);.

Вихід буде виглядати приблизно так:

1.7.0_45         > 1.007000045
ver 1.7.build_45 > 1.007000045
1.234.567.890    > 1.23456789

0

У мене була така ж проблема порівняння версій, але з версіями, можливо, що містять щось (тобто: роздільники, які не були крапками, розширення типу rc1, rc2 ...).

Я використав це, яке в основному розділяє рядки версії на числа і не числа, і намагається порівняти відповідно до типу.

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;}
    }
  }
}

Тут є деякі припущення для деяких випадків, наприклад: "1,01" === "1,1", або "1,8" <"1,71". Він не вдається керувати "1.0.0-rc.1" <"1.0.0", як зазначено в семантичній версії 2.0.0


0

Попередня обробка версій до сортування означає, що parseInt не викликається кілька разів без потреби. Використовуючи карту масиву #, схожу на пропозицію Майкла Діла, ось такий варіант я використовую для пошуку новітньої версії стандартного 3-х частинного севера:

var semvers = ["0.1.0", "1.0.0", "1.1.0", "1.0.5"];

var versions = semvers.map(function(semver) {
    return semver.split(".").map(function(part) {
        return parseInt(part);
    });
});

versions.sort(function(a, b) {
    if (a[0] < b[0]) return 1;
    else if (a[0] > b[0]) return -1;
    else if (a[1] < b[1]) return 1;
    else if (a[1] > b[1]) return -1;
    else if (a[2] < b[2]) return 1;
    else if (a[2] > b[2]) return -1;
    return 0;
});

var newest = versions[0].join(".");
console.log(newest); // "1.1.0"

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.