¿Cómo comparar el número de versión del software usando js? (solo número)

164

Aquí está el número de versión del software:

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

¿Cómo puedo comparar esto? Suponga que el orden correcto es:

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

La idea es simple ...: lea el primer dígito, luego el segundo, después el tercero ... Pero no puedo convertir el número de versión en número flotante ... También puede ver el número de versión como esta:

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

y esto es más claro para ver cuál es la idea detrás ... Pero, ¿cómo convertirlo en un programa de computadora? ¿Alguien tiene alguna idea sobre cómo ordenar esto? Gracias.

Tattat
fuente
55
Esta sería una buena pregunta de entrevista tipo fizzbuzz.
Steve Claridge
2
Esta es la razón por la cual todos los números de versión de software deben ser enteros como 2001403. Cuando desea mostrarlo de una manera amigable como "2.0.14.3", formatea el número de versión en el momento de la presentación.
jarmod
2
El problema general aquí son las comparaciones de la versión semántica, y no es trivial (ver # 11 en semver.org ). Afortunadamente, hay una biblioteca oficial para eso, el versionador semántico para npm .
Dan Dascalescu
1
Encontramos un script simple que compara semvers
vsync

Respuestas:

133

La idea básica para hacer esta comparación sería utilizar Array.splitpara obtener matrices de partes de las cadenas de entrada y luego comparar pares de partes de las dos matrices; Si las partes no son iguales, sabemos qué versión es más pequeña.

Hay algunos detalles importantes a tener en cuenta:

  1. ¿Cómo deben compararse las partes de cada par? La pregunta quiere comparar numéricamente, pero ¿qué pasa si tenemos cadenas de versión que no están formadas solo por dígitos (por ejemplo, "1.0a")?
  2. ¿Qué debería pasar si una cadena de versión tiene más partes que la otra? Lo más probable es que "1.0" se considere menos que "1.0.1", pero ¿qué pasa con "1.0.0"?

Aquí está el código para una implementación que puede usar directamente ( resumen con documentación ):

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 versión compara partes naturalmente , no acepta sufijos de caracteres y considera que "1.7" es más pequeño que "1.7.0". El modo de comparación se puede cambiar a cadenas lexicográficas y las versiones más cortas se pueden rellenar con ceros automáticamente utilizando el tercer argumento opcional.

Hay un JSFiddle que ejecuta "pruebas unitarias" aquí ; Es una versión ligeramente ampliada del trabajo de ripper234 (gracias).

Nota importante: este código usa Array.mapy Array.every, lo que significa que no se ejecutará en versiones de IE anteriores a 9. Si necesita admitirlos, deberá proporcionar polyfills para los métodos que faltan.

Jon
fuente
16
Aquí hay una versión mejorada con algunas pruebas unitarias: jsfiddle.net/ripper234/Xv9WL/28
ripper234
55
Hola a todos, he convertido esta esencia en un gitrepo con pruebas y todo, y la puse en npm y bower para poder incluirla en mis proyectos más fácilmente. github.com/gabe0x02/version_compare
Gabriel Littman
2
@GabrielLittman: ¡Hola, gracias por tomarse el tiempo para hacer eso! Sin embargo, todo el código en SO tiene licencia CC-BY-SA de forma predeterminada. Eso significa que no puede hacer que su paquete tenga licencia GPL. Sé que la abogacía no es para lo que nadie está aquí, pero sería bueno que lo arreglaras.
Jon
2
@GabrielLittman: GPL es en realidad muy restrictivo en el sentido de que está obligado a otorgar una licencia GPL a todo el código que entra en contacto con el código GPL existente. De todos modos, para referencia futura: una licencia buena y ampliamente utilizada de "haz lo que quieras, sin condiciones" es MIT .
Jon
3
@GabrielLittman: ya hay bibliotecas establecidas escritas por desarrolladores experimentados que realizan comparaciones semver.
Dan Dascalescu
82

semver

El analizador de la versión semántica utilizado por npm.

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

Enlace de versiones semántico :
https://www.npmjs.com/package/semver#prerelease-identifiers

Mohammed Akdim
fuente
8
Si. Esta es la respuesta correcta: comparar versiones no es trivial (ver # 11 en semver.org ), y hay bibliotecas de nivel de producción que hacen el trabajo.
Dan Dascalescu
77
técnicamente, no son las respuestas correctas, ya que node.js y javascript son diferentes. Supuse que la pregunta original estaba más dirigida al navegador. Pero google me trajo aquí y por suerte estoy usando el nodo :)
Lee Gary
2
NodeJS no es solo una solución del lado del servidor. Electron Framework incrusta un nodeJS para aplicaciones de escritorio. Esta es en realidad la respuesta que estaba buscando.
Anthony Raymond el
2
semver es un paquete npm, ¡se puede usar en cualquier entorno JS!
Esta
44
@artuska, entonces simplemente ve por otro paquete como semver-compare - 233B (¡menos de 0.5kB!) comprimido
:)
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"));
Joe
fuente
Creo que la línea: var len = Math.min(a_components.length, b_components.length);hará que las versiones 2.0.1.1 y 2.0.1 sean tratadas como iguales, ¿verdad?
Jon Egerton
1
No. ¡Cuida el bucle! Si una cadena es un prefijo de la otra (es decir, el bucle llega al final), entonces la más larga se toma como más alta.
Joe
Tal vez te disuadió mi tropiezo sobre el idioma inglés en el comentario ...
Joe
@ Joe, sé que es una respuesta un poco vieja, pero estaba usando la función. Pruebas a = '7'y b = '7.0'devoluciones -1porque 7.0 es más largo. ¿Tienes alguna sugerencia para eso? ( console.log(compare("7", "7.0")); //returns -1)
RaphaelDDL
Supongo que eso viene bajo el título de comportamiento indefinido. Si tiene estos números de versión, estoy seguro de que puede modificar la lógica para que se ajuste a sus requisitos.
Joe
48

Esta función de comparación muy pequeña pero muy rápida toma números de versión de cualquier longitud y cualquier tamaño de número por segmento .

Valores devueltos:
- un número < 0si a <b
- un número > 0si a> b
- 0si a = b

Entonces puede usarlo como función de comparación para Array.sort ();

EDITAR: Versión corregida que elimina los ceros finales para reconocer "1" y "1.0.0" como iguales

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

LeJared
fuente
Falló con '0.0' y '0.0.0'. Ver violín: jsfiddle.net/emragins/9e9pweqg
emragins
1
@emragins ¿Cuándo necesitarías hacer eso?
Skylar Ittner
1
@emragins: no veo dónde falla. Genera ["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"] donde sus salidas de código ["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"] , lo cual es perfectamente lo mismo, ya 0.0 y 0.0.0 son considerados como iguales , lo que significa que es irrelevante si '0.0' está ante '0.0.0' o viceversa.
LeJared
Estoy de acuerdo en que este es un punto habitual. Estoy usando esto con github.com/jonmiles/bootstrap-treeview , que clasifica los nodos de manera similar a las versiones, solo que en realidad son solo nodos principales / secundarios y sus índices. Ex. Padre: 0.0, hijo: 0.0.0, 0.0.1. Consulte este número para obtener más detalles sobre por qué me importa: github.com/jonmiles/bootstrap-treeview/issues/251
emragins
1
Vea la respuesta aquí stackoverflow.com/questions/6611824/why-do-we-need-to-use-radix . Los navegadores más antiguos solían adivinar el parámetro radix si no se especificaba. Un cero inicial en una cadena de números como la parte central en "1.09.12" solía analizarse con radix = 8, lo que resultaba en el número 0 en lugar del número esperado 9.
LeJared
14

Tomado 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;
    }
usuario123444555621
fuente
Simple, pero limitado a tres campos de versión.
Dan Dascalescu
11

No pude encontrar una función haciendo lo que quería aquí. Entonces escribí el mío. Esta es mi contribución. Espero que alguien lo encuentre útil.

Pros:

  • Maneja cadenas de versiones de longitud arbitraria. '1' o '1.1.1.1.1'.

  • El valor predeterminado es 0 si no se especifica. El hecho de que una cadena sea más larga no significa que sea una versión más grande. ('1' debería ser lo mismo que '1.0' y '1.0.0.0'.)

  • Compara números, no cadenas. ('3' <'21' debe ser verdadero. No falso.)

  • No pierdas el tiempo en comparaciones inútiles en el bucle. (Comparando para ==)

  • Puedes elegir tu propio comparador.

Contras:

  • No maneja letras en la cadena de versión. (¿No sé cómo funcionaría eso?)

Mi código, similar a la respuesta aceptada 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);
}

Ejemplos :

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
Viktor
fuente
¡Esta versión es en mi opinión mejor que la de la respuesta aprobada!
user3807877
1
¡Esta función es propensa a la inyección de código si el parámetro de comparación se usa con una entrada de usuario no verificada! Ejemplo: compareVersions ('1.2', '== 0; alert ("cotcha");', '1.2');
LeJared
@LeJared True. Sin embargo, cuando lo escribí, no lo usaríamos con el código enviado por el usuario. Debería haberlo mencionado como una estafa probablemente. Ahora he actualizado el código para eliminar esa posibilidad. Ahora, sin embargo, cuando el paquete webpack y otros paquetes node.js han prevalecido, sugeriría que la respuesta de Mohammed Akdim anterior, usando semver, casi siempre sea la respuesta correcta a esta pregunta.
Viktor
10

Función simple y corta:

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
}

Pruebas:

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
Arthur Araújo
fuente
Puede simplificarlo con: const a = ~~ newParts [i]; De hecho, esta es la forma más eficiente de convertir una cadena en un número entero, que devuelve 0 si la variable no está definida o contiene caracteres no numéricos.
vanowm
5

Perdóname si esta idea ya ha sido visitada en un enlace que no he visto.

He tenido cierto éxito con la conversión de las partes en una suma ponderada de esta manera:

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

Lo que hizo que las comparaciones fueran muy fáciles (comparar un doble). Nuestros campos de versión nunca tienen más de 4 dígitos.

7.10.2.184  -> 7010002184.0
7.11.0.1385 -> 7011001385.0

Espero que esto ayude a alguien, ya que los múltiples condicionales parecen un poco exagerados.

Noxina
fuente
2
Esto se romperá si this.minor> 999 (se superpondrá con mayor)
Afanasii Kurakin
5

Aquí hay otra versión corta que funciona con cualquier número de sub versiones, ceros rellenos e incluso números con 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;
}

Salida:

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/

vanowm
fuente
5

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

El código más simple para los 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;
 }

La idea aquí es comparar números pero en forma de cadena. Para que la comparación funcione, las dos cadenas deben tener la misma longitud. entonces:

"123" > "99" volverse "123" > "099"
relleno del número corto "arreglar" la comparación

Aquí relleno cada parte con ceros a longitudes de 10. luego solo uso una comparación de cadena simple para la respuesta

Ejemplo:

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
pery mimon
fuente
¿explicarías compareVersion2qué funciona exactamente?
Usman Wali
Bien, entonces puedes usar en substringlugar de padStartuna mejor compatibilidad, es decir, var zeros = "0000000000"; '0.2.32'.split('.').map( s => zeros.substring(0, zeros.length-s.length) + s ).join('.') te dará 0000000000.0000000002.0000000032:)
Usman Wali
4

Mi respuesta menos detallada que la mayoría de las respuestas aquí

/**
 * 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
}
Aliado
fuente
1
deberías convertirlo en un módulo y ponerlo en node.js. hasta entonces, estoy robando su código con atribución a usted. gracias por esto.
r3wt
3

Aunque esta pregunta ya tiene mucho respuestas, cada una promueve su propia solución, mientras que tenemos un ecosistema completo de bibliotecas (de batalla) probadas para esto.

Una búsqueda rápida en NPM , GitHub , X nos dará algunas bibliotecas encantadoras, y me gustaría revisar algunas:

semver-comparees una gran lib liviana (~ 230B) que es especialmente útil si desea ordenar por números de versión, como regresa el método expuesto de la biblioteca -1, 0o de manera 1adecuada.

El núcleo de la 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 tiene un tamaño considerable (~ 4.4kB comprimido), pero permite algunas comparaciones únicas y agradables como encontrar el mínimo / máximo de una pila de versiones o averiguar si la versión proporcionada es única o menos que cualquier otra cosa en una colección de versiones.

compare-versionses otra pequeña lib (~ 630B gzipped) y sigue las especificaciones muy bien, lo que significa que puede comparar versiones con banderas alfa / beta e incluso comodines (como para versiones menores / parche: 1.0.xo1.0.* )

El punto es: no siempre es necesario copiar y pegar el código de StackOverflow, si puede encontrar versiones decentes probadas (por unidad) a través del administrador de paquetes de su elección.

kano
fuente
3

Me enfrenté a un problema similar y ya había creado una solución para ello. Siéntase libre de probarlo.

Vuelve 0para equal, 1si la versión es greatery -1si esless

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

Dev impresionante
fuente
2

La idea es comparar dos versiones y saber cuál es la más grande. Eliminamos "." y comparamos cada posición del vector con la otra.

// 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;
      }
   }
}
Bagazo
fuente
Respuesta épica, exactamente lo que estaba buscando.
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
    }
Dyllan M
fuente
1
Bienvenido a SO. Esta pregunta ya tiene muchas buenas respuestas, por favor abstenerse de agregar nuevas respuestas a menos que agregue algo nuevo.
ext
1

La replace()función solo reemplaza la primera aparición en la cadena. Entonces, reemplacemos .con ,. Luego borre todo .y vuelva ,a hacer el para .analizarlo para que flote.

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

finalmente, ordénelo:

versions.sort();
Sascha Galley
fuente
1

Mira esta publicación de blog . Esta función funciona para números de versión 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
David
fuente
1

Si, por ejemplo, queremos verificar si la versión actual de jQuery es inferior a 1.8, parseFloat($.ui.version) < 1.8 )daría un resultado incorrecto si la versión es "1.10.1", ya que devuelve parseFloat ("1.10.1") 1.1. Una comparación de cadenas también iría mal, ya que se "1.8" < "1.10"evalúa comofalse .

Entonces necesitamos una prueba como esta

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

La siguiente función maneja esto correctamente:

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

Aquí hay unos ejemplos:

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

Vea aquí una muestra en vivo y un conjunto de pruebas: http://jsfiddle.net/mar10/8KjvP/

mar10
fuente
arghh, acabo de notar que ripper234 había publicado una URL de violín en eof de los comentarios hace unos meses que es bastante similar. De todos modos, mantengo mi respuesta aquí ...
mar10
Este también fallará (como ocurre con la mayoría de las variantes) en estos casos: versionCompare ('1.09', '1.1') devuelve "1", de la misma manera que versionCompare ('1.702', '1.8').
shaman.sir
El código evalúa "1.09"> "1.1" y "1.702"> "1.8", lo que creo que es correcto. Si no está de acuerdo: ¿puede señalar algún recurso que respalde su opinión?
mar10
Depende de tus principios, como sé, no hay una regla estricta o algo así. Con respecto a los recursos, el artículo de Wikipedia para "Versiones de software" en "Incremento de secuencias" dice que 1.81 puede ser una versión menor de 1.8, por lo que 1.8 debería leerse como 1.80. El artículo de versión semántica semver.org/spec/v2.0.0.html también dice que 1.9.0 -> 1.10.0 -> 1.11.0, por lo que 1.9.0 se trata como 1.90.0 en comparación de esta manera. Entonces, siguiendo esta lógica, la versión 1.702 era anterior a la versión 1.8, que se trata como 1.800.
shaman.sir
1
Veo que algunas reglas tratan 1.8 <1.81 <1.9. Pero en semver usarías 1.8.1 en lugar de 1.81. Semver (según tengo entendido) se define alrededor del supuesto de que incrementar una parte siempre generará una versión 'posterior', por lo que 1.8 <1.8.1 <1.9 <1.10 <1.81 <1.90 <1.100. Tampoco veo una indicación de que esto esté limitado a dos dígitos. Entonces diría que mi código es totalmente compatible con semver.
mar10
1

Aquí hay una implementación de coffeescript adecuada para usar con Array.sort inspirada en otras respuestas aquí:

# 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
LOAS
fuente
Esto está inspirado en la respuesta de LeJared .
Dan Dascalescu
esto no funciona correctamente ... aquí está el resultado ... resultado ['1.1.1', '2.1.1', '3.3.1.0', '3.1.1.0']
ertan2002
1

Escribí un módulo de nodo para ordenar las versiones, puedes encontrarlo aquí: version-sort

Caracteristicas :

  • sin límite de secuencias '1.0.1.5.53.54654.114.1.154.45' funciona
  • sin límite de longitud de secuencia: '1.1546515465451654654654654138754431574364321353734' funciona
  • puede ordenar objetos por versión (ver README)
  • etapas (como alfa, beta, rc1, rc2)

No dude en abrir un problema si necesita otra función.

Quentin Rossetti
fuente
1

Esto funciona para versiones numéricas de cualquier longitud separadas por un punto. Devuelve verdadero solo si myVersion es> = minimumVersion, suponiendo que la versión 1 es menor que 1.0, la versión 1.1 es menor que 1.1.0 y así sucesivamente. Debería ser bastante simple agregar condiciones adicionales como aceptar números (simplemente convertir a una cadena) y hexadecimal o hacer que el delimitador sea dinámico (solo agregue un parámetro delimitador y luego reemplace "." Con el 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);
}

Aquí hay algunas pruebas:

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

Alternativamente, aquí hay una versión 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);
}
Will Schulz
fuente
1

Encuentro la forma más simple de compararlos, no estoy seguro si es lo que quieres. cuando ejecuto el siguiente código en la consola, tiene sentido, y usando el método sort (), podría obtener la serie ordenada de cadenas de versiones. Se basa en el orden alfabético.

"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"]
por qué
fuente
3
No funciona bien para números de versión de dos dígitos, por ejemplo, 1.10.0.
Leukipp
1

Podrías usar String#localeCompareconoptions

sensibilidad

Qué diferencias en las cadenas deberían conducir a valores de resultado distintos de cero. Los valores posibles son:

  • "base": Solo las cadenas que difieren en letras base se comparan como desiguales. Ejemplos: a ≠ b, a = á, a = A.
  • "accent": Solo las cadenas que difieren en letras base o acentos y otras marcas diacríticas se comparan como desiguales. Ejemplos: a ≠ b, a ≠ á, a = A.
  • "case": Solo las cadenas que difieren en letras base o mayúsculas y minúsculas se comparan como desiguales. Ejemplos: a ≠ b, a = á, a ≠ A.
  • "variant": Las cadenas que difieren en letras base, acentos y otras marcas diacríticas, o en mayúsculas y minúsculas se comparan como desiguales. También se pueden tener en cuenta otras diferencias. Ejemplos: a ≠ b, a ≠ á, a ≠ A.

El valor predeterminado es "variante" para el uso "ordenar"; depende de la configuración regional para el uso "buscar".

numérico

Si se debe utilizar la intercalación numérica, de modo que "1" <"2" <"10". Los valores posibles son truey false; el valor predeterminado es false. Esta opción se puede establecer mediante una propiedad de opciones o mediante una clave de extensión Unicode; si se proporcionan ambos, la optionspropiedad tiene prioridad. No se requieren implementaciones para admitir esta propiedad.

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

Nina Scholz
fuente
¿Cómo funciona esto realmente? ¿Qué es lo undefinedanterior, Idioma? ¿Cómo es que logras publicar esto mientras leo los otros;)
mplungjan
undefinedes la parte local, no se usa aquí.
Nina Scholz
0

¿no podrías convertirlos en números y luego ordenarlos por tamaño? Agregue 0 a los que están a los números que son <4 de longitud

jugado en la consola:

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

cuanto mayor sea la versión, mayor número. Editar: probablemente deba ajustarse para tener en cuenta la serie de versiones más grandes

Contra
fuente
Ese fue solo un ejemplo, ya que él mismo tiene que hacer algunas cosas: P En lugar de 4, obtenga la cantidad de números que tiene la versión más grande, luego complete los más bajos que eso con 0's
Contra
0

Este es un buen truco. Si está tratando con valores numéricos, entre un rango específico de valores, puede asignar un valor a cada nivel del objeto de versión. Por ejemplo, "greatestValue" se establece en 0xFF aquí, lo que crea un aspecto muy "IP" en su versión.

Esto también maneja versiones alfanuméricas (es decir, 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);
Michael Deal
fuente
0

Me gusta la versión de @ mar10 , aunque desde mi punto de vista, existe la posibilidad de mal uso (parece que no es el caso si las versiones son compatibles con el documento de versiones semánticas , pero puede ser el caso si se usa algún "número de compilación" ):

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

El problema aquí es que los subnúmeros del número de versión, en algunos casos, se escriben con ceros finales cortados (al menos como lo vi recientemente mientras uso un software diferente), que es similar a la parte racional de un número, entonces:

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

Sin embargo, el primer (o ambos, primer y segundo) subnúmero de versión siempre se trata como un valor entero al que realmente equivale.

Si usa este tipo de versiones, puede cambiar solo unas pocas líneas en el ejemplo:

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

Así que cada sub-serie excepto el primero se comparó como un flotador, así 09y 1se convertirá en 0.09y 0.1por consiguiente y en comparación adecuadamente de esta manera. 2054y 3se convertirá 0.2054y0.3 .

La versión completa, entonces, es (créditos a @ 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;
}

PD: es más lento, pero también es posible pensar en reutilizar la misma función de comparación que opera el hecho de que la cadena es en realidad la 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;
     }
 }
chamán señor
fuente
0

Hice esto basado en la idea de Kons, y lo optimicé para la versión de Java "1.7.0_45". Es solo una función destinada a convertir una cadena de versión en un flotante. Esta es la función:

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

La cadena "1.7.0_45" se convierte a 1.0070000450000001 y esto es lo suficientemente bueno para una comparación normal. Error explicado aquí: ¿Cómo lidiar con la precisión del número de coma flotante en JavaScript? . Si necesita más de 3 dígitos en cualquier parte, puede cambiar el divisorMath.pow(10, i * 3); .

La salida se verá así:

1.7.0_45         > 1.007000045
ver 1.7.build_45 > 1.007000045
1.234.567.890    > 1.23456789
Adrian S
fuente
0

Tuve el mismo problema de comparación de versiones, pero con versiones que posiblemente contienen algo (es decir: separadores que no eran puntos, extensiones como rc1, rc2 ...).

Usé esto, que básicamente divide las cadenas de versión en números y no números, e intenta comparar en consecuencia con el 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;}
    }
  }
}

Aquí hay algunos supuestos para algunos casos, por ejemplo: "1.01" === "1.1" o "1.8" <"1.71". No puede administrar "1.0.0-rc.1" <"1.0.0", según lo especificado por Semantic versionning 2.0.0

Uriel
fuente
0

El preprocesamiento de las versiones anteriores a la clasificación significa que parseInt no se llama varias veces innecesariamente. Usando Array # map similar a la sugerencia de Michael Deal, aquí hay un tipo que uso para encontrar la versión más nueva de un semver estándar 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"

theGecko
fuente