Como comparar o número da versão do software usando js? (apenas número)


164

Aqui está o número da versão do software:

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

Como posso comparar isso? Suponha que a ordem correta seja:

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

A idéia é simples ...: Leia o primeiro dígito, depois o segundo, depois o terceiro .... Mas não consigo converter o número da versão em número flutuante .... Você também pode ver o número da versão como isto:

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

e isso é mais claro para ver qual é a idéia por trás ... Mas, como convertê-lo em um programa de computador? Alguém tem alguma idéia de como classificar isso? Obrigado.


5
Essa seria uma boa pergunta para uma entrevista do tipo "fizzbuzz".
26611 Steve Claridge

2
É por isso que todos os números de versão do software devem ser números inteiros como 2001403. Quando você deseja exibi-lo de alguma maneira amigável como "2.0.14.3", formata o número da versão no momento da apresentação.
jarmod

2
O problema geral aqui são as comparações de versões semânticas e não é trivial (consulte o item 11 em semver.org ). Felizmente, existe uma biblioteca oficial para isso, o versionador semântico para o npm .
Dan Dascalescu 02/09/2015

1
Encontrou um script simples que compara semvers
vsync

Respostas:


133

A idéia básica para fazer essa comparação seria usar Array.splitpara obter matrizes de partes das seqüências de entrada e comparar pares de partes das duas matrizes; se as partes não forem iguais, sabemos qual versão é menor.

Há alguns detalhes importantes a serem lembrados:

  1. Como as partes de cada par devem ser comparadas? A questão quer comparar numericamente, mas e se tivermos seqüências de versão que não são compostas apenas por dígitos (por exemplo, "1.0a")?
  2. O que deve acontecer se uma string de versão tiver mais partes que a outra? Provavelmente "1.0" deve ser considerado menor que "1.0.1", mas e "1.0.0"?

Aqui está o código para uma implementação que você pode usar diretamente (lista com documentação ):

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

Esta versão compara partes naturalmente , não aceita sufixos de caracteres e considera "1.7" menor que "1.7.0". O modo de comparação pode ser alterado para lexicográfico e as seqüências de versão mais curtas podem ser preenchidas com zero automaticamente usando o terceiro argumento opcional.

Existe um JSFiddle que executa "testes de unidade" aqui ; é uma versão ligeiramente expandida do trabalho de ripper234 (obrigado).

Nota importante: Este código usa Array.mape Array.every, o que significa que ele não será executado nas versões do IE anteriores a 9. Se você precisar dar suporte a eles, precisará fornecer polyfills para os métodos ausentes.


16
Aqui está uma versão melhorada com alguns testes de unidade: jsfiddle.net/ripper234/Xv9WL/28
ripper234

5
Ei, pessoal, eu coloquei essa essência em um gitrepo com testes e tudo mais e a coloquei em npm e bower para que eu possa incluí-la em meus projetos com mais facilidade. github.com/gabe0x02/version_compare
Gabriel Littman

2
@ GabrielLittman: Ei, obrigado por dedicar um tempo para fazer isso! No entanto, todo o código no SO é licenciado com o CC-BY-SA por padrão. Isso significa que você não pode ter seu pacote licenciado pela GPL. Eu sei que advocacia não é para isso que ninguém está aqui, mas seria bom se você a corrigisse.
Jon

2
@ GabrielLittman: A GPL é realmente muito restritiva no sentido de que você é forçado a licenciar a GPL todo o código que entra em contato com o código GPL existente. Enfim, para referência futura: uma licença boa e amplamente usada "faça o que você quiser, sem compromisso" é o MIT .
9134 Jon

3
@ GabrielLittman: já existem bibliotecas estabelecidas, escritas por desenvolvedores experientes que realizam comparações semestrais.
Dan Dascalescu 01/09/2015

82

sempre

O analisador de versão semântica usado pelo npm.

$ npm install semprever

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, '*')

Link de versão semântica :
https://www.npmjs.com/package/semver#prerelease-identifiers


8
Sim. Esta é a resposta correta - a comparação de versões não é trivial (consulte o nº 11 em semver.org ) e existem bibliotecas em nível de produção que fazem o trabalho.
Dan Dascalescu 02/09/2015

7
tecnicamente, não é a resposta certa, já que node.js e javascript são diferentes. Eu supunha que a pergunta original era mais direcionada para o navegador. Mas o Google trouxe-me aqui e felizmente eu estou usando nó :)
Lee Gary

2
O NodeJS não é apenas uma solução apenas do lado do servidor. A estrutura eletrônica incorporou um nodeJS para aplicativos de desktop. Esta é realmente a resposta que eu estava procurando.
Anthony Raymond

2
sempre que é um pacote npm, pode ser usado em qualquer ambiente JS! Esta é a resposta certa
Neiker

4
@artuska bem, então simplesmente ir para outro pacote como -semver comparar - 233B (menos de 0.5kB!) gzipped:)
Kano

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

Eu acho que a linha: var len = Math.min(a_components.length, b_components.length);fará com que as versões 2.0.1.1 e 2.0.1 sejam tratadas como iguais, não é?
31811 Jon Egerton

1
Não. Olhe logo após o loop! Se uma string é o prefixo da outra (ou seja, o loop chega ao fim), a mais longa é considerada mais alta.
3126 Joe

Talvez você foram adiadas minha tropeçando sobre o idioma Inglês no comentário ...
Joe

@ Joe Eu sei que é uma resposta um pouco antiga, mas eu estava usando a função. Testes a = '7'e b = '7.0'devoluções -1porque 7.0 é mais longo. Tem alguma sugestão para isso? ( console.log(compare("7", "7.0")); //returns -1)
RaphaelDDL

Suponho que isso se enquadre no comportamento indefinido. Se você possui esses números de versão, tenho certeza de que pode modificar a lógica para atender aos seus requisitos.
18713 Joe

48

Essa função de comparação muito pequena, mas muito rápida, aceita números de versão de qualquer tamanho e qualquer tamanho de número por segmento .

Valores de retorno:
- um número < 0se a <b
- um número > 0se a> b
- 0se a = b

Então você pode usá-lo como função de comparação para Array.sort ();

EDIT: Versão corrigida com bug removendo zeros à direita para reconhecer "1" e "1.0.0" como iguais

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


Falha com '0.0' e '0.0.0'. Veja fiddle: jsfiddle.net/emragins/9e9pweqg
emragins

1
@emragins Quando você precisaria fazer isso?
Skylar Ittner

1
@ emragins: não vejo onde falha. Ele gera ["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"] onde o seu código gera ["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"] , o que é perfeitamente o mesmo, já que 0,0 e 0,0,0 são considerados iguais , o que significa que é irrelevante se '0,0' é anterior a '0,0,0' ou vice-versa.
LeJared 14/09/16

Eu concordo que este é um ponto usual. Estou usando isso com github.com/jonmiles/bootstrap-treeview , que classifica os nós de maneira semelhante às versões, mas na verdade são apenas nós pai / filho e seus índices. Ex. Pai: 0.0, filho: 0.0.0, 0.0.1. Veja esta edição para obter mais detalhes sobre por que eu me importo: github.com/jonmiles/bootstrap-treeview/issues/251
emragins

1
Consulte a resposta aqui stackoverflow.com/questions/6611824/why-do-we-need-to-use-radix . Navegadores mais antigos costumavam adivinhar o parâmetro radix, se não especificado. Um zero inicial de uma cadeia de número como a parte do meio em "1.09.12" utilizado para ser analisado com base = 8 resultando em número 0 em vez do esperado número 9.
LeJared

14

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

Simples, mas limitado a três campos de versão.
Dan Dascalescu 01/09/2015

11

Não foi possível encontrar uma função fazendo o que eu queria aqui. Então eu escrevi o meu. Esta é a minha contribuição. Espero que alguém ache útil.

Prós:

  • Lida com seqüências de versão de comprimento arbitrário. '1' ou '1.1.1.1.1'.

  • O padrão é cada valor para 0 se não for especificado. Só porque uma string é mais longa não significa que é uma versão maior. ('1' deve ser o mesmo que '1.0' e '1.0.0.0'.)

  • Compare números e não cadeias. ('3' <'21' deve ser verdadeiro. Não falso.)

  • Não perca tempo com comparações inúteis no loop. (Comparando para ==)

  • Você pode escolher seu próprio comparador.

Contras:

  • Ele não manipula letras na cadeia de versão. (Eu não sei como isso funcionaria?)

Meu código, semelhante à resposta aceita por 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);
}

Exemplos :

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

esta versão é na minha opinião melhor do que a da resposta aprovada!
user3807877

1
Esta função é propensa à injeção de código se o parâmetro comparador for usado com entrada não verificada do usuário! Exemplo: compareVersions ('1.2', '== 0; alert ("cotcha");', '1.2');
LeJared

@LeJared True. Quando eu o escrevi, não o usaríamos com o código enviado pelo usuário. Deveria ter trazido isso à tona como um golpe, provavelmente. Atualizei o código para eliminar essa possibilidade. Agora, no entanto, quando o webpack e outros empacotadores node.js se tornarem predominantes, eu sugeriria que a resposta de Mohammed Akdim acima, usando semver, seria quase sempre a resposta correta para esta pergunta.
Viktor

10

Função simples e curta:

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
}

Testes:

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

Você pode simplificá-lo com: const a = ~~ newParts [i]; De fato, esta é a maneira mais eficiente de converter uma string em um número inteiro, que retorna 0 se a variável não for definida ou contiver caracteres não numéricos.
vanowm

5

Perdoe-me se essa idéia já foi visitada em um link que eu não vi.

Eu tive algum sucesso com a conversão das partes em uma soma ponderada da seguinte forma:

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

O que tornou as comparações muito fáceis (comparando um duplo). Nossos campos de versão nunca têm mais que 4 dígitos.

7.10.2.184  -> 7010002184.0
7.11.0.1385 -> 7011001385.0

Espero que isso ajude alguém, pois os vários condicionais parecem um pouco exagerados.


2
Isto vai quebrar, se this.minor> 999 (vai sobrepor-se com grande)
Afanasii Kurakin

5

Aqui está outra versão curta que funciona com qualquer número de sub-versões, zeros acolchoados e números pares com letras (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;
}

Resultado:

0 : a = b

1 : a> b

-1 : a <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 resposta:

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

Código mais simples para navegadores modernos:

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

A idéia aqui é comparar números, mas na forma de string. para fazer a comparação funcionar, as duas cadeias devem ter o mesmo comprimento. tão:

"123" > "99"tornar-se "123" > "099"
preenchendo o número curto "consertar" a comparação

Aqui, preencho cada parte com zeros com comprimentos de 10. Em seguida, basta usar uma comparação simples para a resposta

Exemplo:

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

você explicaria a função o compareVersion2que exatamente acontece?
Usman Wali

Bom, então você pode usar substringem vez de padStartpara uma melhor compatibilidade isto var zeros = "0000000000"; '0.2.32'.split('.').map( s => zeros.substring(0, zeros.length-s.length) + s ).join('.') lhe dará 0000000000.0000000002.0000000032:)
Usman Wali


4

Minha resposta menos detalhada do que a maioria das respostas aqui

/**
 * 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
você deve transformá-lo em um módulo e colocá-lo no node.js. até então, estou roubando seu código com atribuição a você. Obrigado por isso.
R3wt 11/05/19

3

Embora essa pergunta já tenha muitas respostas, cada uma promove sua própria solução preparada no quintal, enquanto temos todo um ecossistema de bibliotecas testadas para isso.

Uma pesquisa rápida no NPM , GitHub , X nos dará algumas bibliotecas adoráveis, e eu gostaria de ler algumas:

semver-compareé uma ótima biblioteca leve (~ 230B) que é especialmente útil se você deseja classificar por números de versão, à medida que o método exposto da biblioteca retorna -1, 0ou de forma 1apropriada.

O núcleo da lib:

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 é bastante robusto em tamanho (~ 4.4kB gzipped), mas permite algumas comparações únicas, como encontrar o mínimo / máximo de uma pilha de versões ou descobrir se a versão fornecida é única ou menor do que qualquer outra coisa em uma coleção de versões.

compare-versionsé outra outra pequena lib (~ 630B compactada com gzip) e segue bem as especificações, o que significa que você pode comparar versões com sinalizadores alfa / beta e até curingas (como nas versões secundárias / patches: 1.0.xou 1.0.*)

Aponte o ponto: nem sempre é necessário copiar e colar o código do StackOverflow, se você pode achar decente (unidade) testado versões através do seu gerenciador de pacotes de sua escolha.


3

Eu enfrentei um problema semelhante e já havia criado uma solução para ele. Sinta-se livre para experimentá-lo.

Retorna 0para equal, 1se a versão forgreater e -1se forless

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

A idéia é comparar duas versões e saber qual é a maior. Nós excluímos "." e comparamos cada posição do vetor com a outra.

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

Resposta épica, exatamente o que eu estava procurando.
Vince

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
Bem-vindo ao SO. Esta pergunta já tem muitas respostas boas, evite adicionar novas respostas, a menos que você adicione algo novo.
ext

1

A replace()função substitui apenas a primeira ocorrência na sequência. Então, vamos substituir o .com ,. Depois exclua tudo .e faça o ,para .novamente e analise-o para flutuar.

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

finalmente, classifique-o:

versions.sort();

1

Confira esta postagem no blog . Esta função funciona para números de versão numéricos.

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

Se, por exemplo, quisermos verificar se a versão atual do jQuery é menor que 1,8, parseFloat($.ui.version) < 1.8 )daria um resultado errado se a versão for "1.10.1", pois o retorno de parseFloat ("1.10.1") 1.1. Uma comparação de string também daria errado, pois "1.8" < "1.10"avalia comofalse .

Então, precisamos de um teste como este

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

A seguinte função lida com isso corretamente:

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

aqui estão alguns exemplos:

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

Veja aqui uma amostra ao vivo e um conjunto de testes: http://jsfiddle.net/mar10/8KjvP/


Agora, notei que o ripper234 havia postado uma URL de violino nos comentários de alguns meses atrás, que é bastante semelhante. De qualquer forma, eu manter a minha resposta aqui ...
Mar10

Este também falhará (como a maioria das variantes) nesses casos: versionCompare ('1.09', '1.1') retorna "1", da mesma forma que versionCompare ('1.702', '1.8').
shaman.sir

O código avalia "1.09"> "1.1" e "1.702"> "1.8", o que acho correto. Se você não concorda: pode apontar para algum recurso que apóie sua opinião?
precisa saber é

Depende dos seus princípios - como eu sei, não existe uma regra estrita ou algo assim. Em relação aos recursos, o artigo da wikipedia para "Versão de software" em "Incrementando sequências" diz que 1,81 pode ser uma versão secundária do 1,8, portanto o 1,8 deve ser 1,80. O artigo de versão semântica semver.org/spec/v2.0.0.html também diz que 1.9.0 -> 1.10.0 -> 1.11.0, então o 1.9.0 é tratado como 1.90.0 em comparação como esse. Portanto, seguindo essa lógica, a versão 1.702 era anterior à versão 1.8, que é tratada como 1.800.
shaman.sir

1
Vejo que algumas regras tratam 1,8 <1,81 <1,9. Mas, no entanto, você usaria 1.8.1 em vez de 1.81. Semver (como eu o entendo) é definido em torno da suposição de que incrementar uma peça sempre gerará uma versão 'posterior', então 1,8 <1,8,1 <1,9 <1,10 <1,81 <1,90 <1,100. Também não vejo indicação de que isso seja limitado a dois dígitos. Então, eu diria que meu código é totalmente compatível com semver.
Mar10

1

Aqui está uma implementação de coffeescript adequada para uso com o Array.sort inspirado em outras respostas aqui:

# 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


isto não está funcionando corretamente .. aqui está o resultado .. result ['1.1.1', '2.1.1', '3.3.1.0', '3.1.1.0'] '
ertan2002 23/08/19

1

Eu escrevi um módulo de nó para classificar versões, você pode encontrá-lo aqui: version-sort

Características :

  • nenhum limite de sequências '1.0.1.5.53.54654.114.1.154.45' funciona
  • sem limite de comprimento de sequência: '1.1546515465451654654654654138754431574364321353734' funciona
  • pode classificar objetos por versão (consulte o arquivo LEIA-ME)
  • estágios (como alfa, beta, rc1, rc2)

Não hesite em abrir um problema se precisar de outro recurso.


1

Isso funciona para versões numéricas de qualquer tamanho, separadas por um ponto. Ele retornará true somente se myVersion for> = minimumVersion, assumindo que a versão 1 seja menor que 1.0, a versão 1.1 seja menor que 1.1.0 e assim por diante. Deve ser bastante simples adicionar condições extras, como aceitar números (apenas converter em uma string) e hexadecimal ou dinamizar o delimitador (basta adicionar um parâmetro de delimitador e substituir o "." Pelo parâmetro)

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

Aqui estão alguns testes:

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

Como alternativa, aqui está uma versão recursiva

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

Acho uma maneira mais simples de compará-los, não tenho certeza se é o que você deseja. quando eu corro abaixo do código no console, faz sentido, e usando o método sort (), eu poderia obter a sequência ordenada de versões das strings. é baseado na ordem alfabética.

"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
Não funciona bem para números de versão de dois dígitos, por exemplo, 1.10.0.
Leukipp

1

Você poderia usar String#localeComparecomoptions

sensibilidade

Quais diferenças nas seqüências devem levar a valores de resultado diferentes de zero. Os valores possíveis são:

  • "base": Somente as strings que diferem nas letras base são comparadas como desiguais. Exemplos: a ≠ b, a = á, a = A.
  • "accent": Somente as strings que diferem em letras base ou acentos e outras marcas diacríticas são comparadas como desiguais. Exemplos: a ≠ b, a ≠ á, a = A.
  • "case": Apenas cadeias que diferem em letras base ou maiúsculas são comparadas como desiguais. Exemplos: a ≠ b, a = á, a ≠ A.
  • "variant": Seqüências de caracteres que diferem em letras base, acentos e outras marcas diacríticas ou maiúsculas e minúsculas são comparadas como desiguais. Outras diferenças também podem ser levadas em consideração. Exemplos: a ≠ b, a ≠ á, a ≠ A.

O padrão é "variante" para uso "classificação"; depende da localidade para o uso "pesquisa".

numérico

Se o agrupamento numérico deve ser usado, de modo que "1" <"2" <"10". Os valores possíveis são truee false; o padrão é false. Esta opção pode ser definida através de uma propriedade options ou através de uma chave de extensão Unicode; se ambos forem fornecidos, a optionspropriedade terá precedência. Implementações não são necessárias para suportar esta propriedade.

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


Como isso realmente funciona? Qual é o undefinedidioma acima? Como é que você consegue postar isso enquanto eu leio os outros;)
mplungjan

undefinedé a parte das localidades, não é usada aqui.
Nina Scholz

0

você não poderia convertê-los em números e depois classificar após o tamanho? Anexar 0 aos números com comprimento <4

brincou no console:

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

quanto maior a versão, maior o número. Editar: provavelmente precisa ser ajustado para incluir séries de versões maiores


Isso foi apenas um exemplo, como ele tem que fazer algumas coisas a si mesmo: P Em vez de 4, obter a quantidade de números a maior versão foi, em seguida, preencha as mais baixas do que com 0 do
Contra

0

Este é um truque legal. Se você estiver lidando com valores numéricos, entre um intervalo específico de valores, poderá atribuir um valor a cada nível do objeto de versão. Por exemplo, "largestValue" está definido como 0xFF aqui, o que cria uma aparência muito "IP" para seu controle de versão.

Isso também lida com versões alfanuméricas (ou seja, 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

Gosto da versão do @ mar10 , embora, do meu ponto de vista, exista uma chance de uso indevido (parece que não é o caso se as versões são compatíveis com o documento Semantic Versioning , mas pode ser o caso se algum "número de compilação" for usado ):

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

O problema aqui é que os subnúmeros do número da versão são, em alguns casos, escritos com zeros à direita cortados (pelo menos como eu o vi recentemente usando um software diferente), o que é semelhante à parte racional de um número, portanto:

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

O primeiro (ou o primeiro e o segundo) subnúmero da versão, no entanto, sempre é tratado como um valor inteiro ao qual realmente é igual.

Se você usar esse tipo de controle de versão, poderá alterar apenas algumas linhas no exemplo:

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

Assim, cada sub-número exceto o primeiro será comparada como um float, então 09e 1vai se tornar 0.09e 0.1em conformidade e em comparação corretamente desta forma. 2054e 3se tornará 0.2054e 0.3.

A versão completa, então, é (créditos para @ 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: É mais lento, mas também é possível pensar em reutilizar a mesma função de comparação, operando o fato de que a string é realmente a matriz de caracteres:

 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

Fiz isso com base na ideia do Kons e o otimizei para a versão Java "1.7.0_45". É apenas uma função destinada a converter uma string de versão em um float. Esta é a função:

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

A sequência "1.7.0_45" é convertida em 1.0070000450000001 e isso é bom o suficiente para uma comparação normal. Erro explicado aqui: Como lidar com a precisão do número de ponto flutuante em JavaScript? . Se precisar de mais de três dígitos em qualquer parte, você pode alterar o divisor Math.pow(10, i * 3);.

A saída terá a seguinte aparência:

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

0

Eu tive o mesmo problema de comparação de versões, mas com versões possivelmente contendo qualquer coisa (ou seja: separadores que não eram pontos, extensões como rc1, rc2 ...).

Eu usei isso, que basicamente divide as seqüências de versão em números e não-números, e tenta comparar de acordo com o tipo.

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

Existem algumas suposições aqui para alguns casos, por exemplo: "1.01" === "1.1" ou "1.8" <"1.71". Falha ao gerenciar "1.0.0-rc.1" <"1.0.0", conforme especificado pelo Semantic versionning 2.0.0


0

Pré-processar as versões anteriores à classificação significa que parseInt não é chamado várias vezes desnecessariamente. Usando o mapa Array # semelhante à sugestão de Michael Deal, aqui está um tipo que eu uso para encontrar a versão mais recente de um semver padrão de 3 partes:

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"

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.