¿Cómo puedo comparar el número de versión del software usando JavaScript? (sólo números)

8 minutos de lectura

Avatar de usuario de Tattat
tatuaje

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?

Supongamos 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, luego 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 así:

"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 puedo convertirlo en un programa de computadora?

  • Esta sería una buena pregunta de entrevista tipo fizzbuzz.

    –Steve Claridge

    26 de julio de 2011 a las 15:33

  • Esta es la razón por la cual todos los números de versión del software deben ser números enteros como 2001403. Cuando desee mostrarlo de una manera amigable como “2.0.14.3”, entonces formatea el número de versión en el momento de la presentación.

    – jarmod

    10 mayo 2013 a las 21:50

  • El problema general aquí son las comparaciones de versiones semánticas, y no es trivial (ver #11 en semver.org). Afortunadamente, existe una biblioteca oficial para eso, la versionador semántico para npm.

    – Dan Dascalescu

    2 de septiembre de 2015 a las 0:23

  • Encontrado una guion sencillo que compara severs

    – vsync

    5 de enero de 2017 a las 14:16


  • @jarmod entonces tienes 2001403Lo es 2.0.14.3 o 20.1.4.3 o 2.0.1.43? Este enfoque es limitante, si no defectuoso.

    – Vanowm

    26 de octubre de 2020 a las 3:11


Avatar de usuario de Jon
Jon

La idea básica para hacer esta comparación sería utilizar Array.split para 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é sucede si tenemos cadenas de versión que no están compuestas solo por dígitos (por ejemplo, “1.0a”)?
  2. ¿Qué debería suceder si una cadena de versión tiene más partes que la otra? Lo más probable es que “1.0” se considere inferior a “1.0.1”, pero ¿qué pasa con “1.0.0”?

Aquí está el código para una implementación que puede usar directamente (esencia con la 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 de forma natural, 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 lexicográfico y las cadenas de versiones más cortas se pueden rellenar automáticamente con ceros 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 utiliza Array.map y Array.everylo que significa que no se ejecutará en versiones de IE anteriores a la 9. Si necesita admitirlas, deberá proporcionar polyfills para los métodos que faltan.

  • Aquí hay una versión mejorada con algunas pruebas unitarias: jsfiddle.net/ripper234/Xv9WL/28

    – destripador234

    13 de marzo de 2012 a las 10:36

  • Su algoritmo no funciona correctamente si comparamos ‘11.1.2’ con ‘3.1.2’, por ejemplo. Debe convertir las cadenas a enteros antes de compararlas. Por favor arregla esto 😉

    – Tamás Pap

    8 de septiembre de 2012 a las 8:01

  • Hola a todos, he convertido esta esencia en un gitrepo con pruebas y todo y lo puse en npm y bower para poder incluirlo en mis proyectos más fácilmente. github.com/gabe0x02/version_compare

    – Gabriel Litman

    6 de noviembre de 2014 a las 1:10

  • @GabrielLittman: ¡Oye, gracias por tomarte el tiempo para hacer eso! Sin embargo, todo el código en SO tiene licencia con CC-BY-SA por defecto. Eso significa que no puede tener su paquete con licencia GPL. Sé que la abogacía no es para lo que nadie está aquí, pero sería bueno que lo arreglaras.

    – Jon

    6 de noviembre de 2014 a las 9:53

  • @GabrielLittman: ya hay bibliotecas establecidas escritas por desarrolladores experimentados que realizan comparaciones de sever.

    – Dan Dascalescu

    01/09/2015 a las 21:33

  • 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

    26 de julio de 2011 a las 15:47

  • No. ¡Mira justo después del bucle! Si una cadena es un prefijo de la otra (es decir, el bucle llega al final), entonces la más larga se considera más alta.

    – José

    26 de julio de 2011 a las 15:51

  • Quizás te desanimó mi tropiezo con el idioma inglés en el comentario…

    – José

    26 de julio de 2011 a las 16:11


  • @Joe, sé que es una respuesta un poco antigua, pero estaba usando la función. Pruebas a = '7' y b = '7.0' devoluciones -1 porque 7.0 es más largo. ¿Tienes alguna sugerencia para eso? ( console.log(compare("7", "7.0")); //returns -1 )

    – Rafael DDL

    17/09/2013 a las 21:24


  • @RaphaelDDL compara la longitud de ambas matrices y agrega 0 a la más corta hasta que las longitudes sean iguales.

    – Vanowm

    27 de mayo de 2019 a las 1:16

Avatar de usuario de Idan
idan

Lo más sencillo es usar localeCompare :

a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })

Esto devolverá:

  • 0: las cadenas de versión son iguales
  • 1: versión a es mayor que b
  • -1: versión b es mayor que a

  • Esa es la respuesta más simple, ¡me encanta!

    – tibalto

    10 de mayo de 2021 a las 9:14

  • ¿Por qué esto no tiene más votos? ¿Hay algo malo con eso? Parece pasar todas las pruebas que escribí.

    – Juan Mendes

    10 de agosto de 2021 a las 16:24

  • @JuanMendes Respuesta simple, escribí esto 10 años después de publicar la pregunta 🙂 pero es una gran idea, ¡comencemos a votar! 🎉

    – Idán

    12 de agosto de 2021 a las 16:24


  • @Mordred Ese es el comportamiento que espero, no espero que sean tratados de la misma manera. Ver semver.org semver.org/#spec-item-11

    – Juan Mendes

    2 oct 2021 a las 14:44

  • Me encanta, pero desafortunadamente puede pasar esta prueba. 1.0.0-alpha < 1.0.0. Ver semver.org/#spec-item-11

    – Sr. Ming

    17 de mayo a las 3:36

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 < 0 si un – un número > 0 si a > b
0 si a = b

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

  • Esa es la respuesta más simple, ¡me encanta!

    – tibalto

    10 de mayo de 2021 a las 9:14

  • ¿Por qué esto no tiene más votos? ¿Hay algo malo con eso? Parece pasar todas las pruebas que escribí.

    – Juan Mendes

    10 de agosto de 2021 a las 16:24

  • @JuanMendes Respuesta simple, escribí esto 10 años después de publicar la pregunta 🙂 pero es una gran idea, ¡comencemos a votar! 🎉

    – Idán

    12 de agosto de 2021 a las 16:24


  • @Mordred Ese es el comportamiento que espero, no espero que sean tratados de la misma manera. Ver semver.org semver.org/#spec-item-11

    – Juan Mendes

    2 oct 2021 a las 14:44

  • Me encanta, pero desafortunadamente puede pasar esta prueba. 1.0.0-alpha < 1.0.0. Ver semver.org/#spec-item-11

    – Sr. Ming

    17 de mayo a las 3:36

Función simple y breve:

function isNewerVersion (oldVer, newVer) {
  const oldParts = oldVer.split('.')
  const newParts = newVer.split('.')
  for (var i = 0; i < newParts.length; i++) {
    const a = ~~newParts[i] // parse int
    const b = ~~oldParts[i] // parse int
    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

  • Puedes simplificarlo con: const a = ~~nuevaspartes[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

    20 de noviembre de 2018 a las 7:10


  • A menudo necesito saber si es más nuevo o igual, para que mi código pueda decidir si ocultar una característica que no es compatible. ¿No es esa la pregunta que más les interesa?

    – Juan Mendes

    10 de agosto de 2021 a las 16:20

  • Agradable y corto, exactamente lo que estaba buscando. También podrías agregar oldVer.replace(/[^0-9.]/g, '').trim() y newVer.replace(/[^0-9.]/g, '').trim() para manejar versiones candidatas alfa, beta o de lanzamiento que agregan texto como este: `1.0.0-rc’

    – Blizzardengle

    9 de noviembre de 2021 a las 19:10

¿Ha sido útil esta solución?