algoritmo - ¿Cómo puedo comparar números de versión de software usando JavaScript? (solo números)
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?
Asume que el orden correcto es:
"1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"
La idea es simple...: Lee el primer dígito, luego el segundo, después el tercero... Pero no puedo convertir el número de versión a número flotante... También puedes 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?
Respuesta #1
semver
El analizador de versiones semánticas utilizado por npm.
npm install semver
var semver = require('semver');
semver.diff('3.4.5', '4.3.7') //'major'
semver.diff('3.4.5', '3.3.7') //'minor'
semver.gte('3.4.8', '3.4.7') //true
semver.ltr('3.4.8', '3.4.7') //false
semver.valid('1.2.3') // '1.2.3'
semver.valid('a.b.c') // null
semver.clean(' =v1.2.3 ') // '1.2.3'
semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true
semver.gt('1.2.3', '9.8.7') // false
semver.lt('1.2.3', '9.8.7') // true
var versions = [ '1.2.3', '3.4.5', '1.0.2' ]
var max = versions.sort(semver.rcompare)[0]
var min = versions.sort(semver.compare)[0]
var max = semver.maxSatisfying(versions, '*')
Enlace de Versionado Semántico :
https://www.npmjs.com/package/semver#prerelease-identifiers
Respuesta #2
La idea básica para hacer esta comparación sería usar Array.split para obtener arreglos de partes de las cadenas de entrada y luego comparar pares de partes de los dos arreglos; si las partes no son iguales, sabemos cuál versión es más pequeña.
Hay algunos detalles importantes que tener en cuenta:
- ¿Cómo se deben comparar las partes en 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")?
- ¿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 menor que "1.0.1", pero ¿qué pasa con "1.0.0"?
Aquí está el código para una implementación que puedes usar directamente (gist 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 las partes de forma natural, no acepta sufijos de caracteres y considera que "1.7" es menor que "1.7.0". El modo de comparación se puede cambiar a lexicográfico y las cadenas de versión más cortas se pueden rellenar automáticamente con ceros usando 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.every, lo que significa que no funcionará en versiones de IE anteriores a la 9. Si necesitas soportarlas, tendrás que proporcionar polyfills para los métodos que faltan.
Respuesta #3
Lo más sencillo es usar localeCompare :
a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })
Esto devolverá:
0: las cadenas de versión son iguales1: la versiónaes mayor queb-1: la versiónbes mayor quea
Aviso legal: Esto no manejará correctamente las versiones preliminares (como 1.0.1-1)
Respuesta #4
// 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"));
Respuesta #5
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 de retorno:
- un número < 0 si a < b
- un número > 0 si a > b
- 0 si a=b
Así que puedes usarlo como función de comparación para Array.sort();
EDITAR: Versión corregida que elimina 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"]Respuesta #6
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 = ~~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
Respuesta #7
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;
}
Respuesta #8
Aquí hay otra versión corta que funciona con cualquier número de subversiones, ceros rellenados e incluso números con letras (1.0.0b3)
const compareVersions = ((prep, l, i, r) => (a, b) =>
{
a = prep(a);
b = prep(b);
l = Math.max(a.length, b.length);
i = 0;
r = i;
while (!r && i < l)
//convert into integer, uncluding undefined values
r = ~~a[i] - ~~b[i++];
return r < 0 ? -1 : (r ? 1 : 0);
})(t => ("" + t)
// treat non-numerical characters as lower version
// replacing them with a negative number based on charcode of first character
.replace(/[^\d.]+/g, c => "." + (c.replace(/[\W_]+/, "").toUpperCase().charCodeAt(0) - 65536) + ".")
// remove trailing "." and "0" if followed by non-numerical characters (1.0.0b);
.replace(/(?:\.0+)*(\.-\d+(?:\.\d+)?)\.*$/g, "$1")
// return array
.split("."));
La función devuelve:
0 si a=b
1 si a > b
-1 si a < b
1.0 = 1.0.0.0.0.0
1.0 < 1.0.1
1.0b1 < 1.0
1.0b = 1.0b
1.1 > 1.0.1b
1.1alpha < 1.1beta
1.1rc1 > 1.1beta
1.1rc1 < 1.1rc2
1.1.0a1 < 1.1a2
1.1.0a10 > 1.1.0a1
1.1.0alpha = 1.1a
1.1.0alpha2 < 1.1b1
1.0001 > 1.00000.1.0.0.0.01
Lo que hace es reemplazar los caracteres no numéricos con un número negativo basado en el código de carácter de la primera letra, divide la cadena en un array y luego compara cada elemento entre los arrays.
Utiliza el truco ~~ que convierte cualquier cosa en un número entero, por lo tanto no necesitamos preocuparnos por longitudes de arrays desiguales.
/*use strict*/
const compareVersions = ((prep, l, i, r) => (a, b) =>
{
a = prep(a);
b = prep(b);
l = Math.max(a.length, b.length);
i = 0;
r = i;
while (!r && i < l)
//convert into integer, uncluding undefined values
r = ~~a[i] - ~~b[i++];
return r < 0 ? -1 : (r ? 1 : 0);
})(t => ("" + t)
// treat non-numerical characters as lower version
// replacing them with a negative number based on charcode of first character
.replace(/[^\d.]+/g, c => "." + (c.replace(/[\W_]+/, "").toUpperCase().charCodeAt(0) - 65536) + ".")
// remove trailing "." and "0" if followed by non-numerical characters (1.0.0b);
.replace(/(?:\.0+)*(\.-\d+(?:\.\d+)?)\.*$/g, "$1")
// return array
.split("."));
//examples
let list = [
["1.0", "1.0.0.0.0.0"],
["1.0", "1.0.1"],
["1.0b1", "1.0"],
["1.0b", "1.0b"],
["1.1", "1.0.1b"],
["1.1alpha", "1.1beta"],
["1.1rc1", "1.1beta"],
["1.1rc1", "1.1rc2"],
["1.1.0a1", "1.1a2"],
["1.1.0a10", "1.1.0a1"],
["1.1.0alpha", "1.1a"],
["1.1.0alpha2", "1.1b1"],
["1.0001", "1.00000.1.0.0.0.01"]
]
for(let i = 0; i < list.length; i++)
{
console.log( list[i][0] + " " + "<=>"[compareVersions(list[i][0], list[i][1]) + 1] + " " + list[i][1] );
}https://jsfiddle.net/vanowm/p7uvtbor/
Respuesta #9
No pude encontrar una función que hiciera lo que quería aquí. Así que escribí la mía propia. Esta es mi contribución. Espero que alguien la encuentre útil.
Ventajas:
Maneja cadenas de versiones de longitud arbitraria. '1' o '1.1.1.1.1'.
Asigna a cada valor 0 si no se especifica. Que una cadena sea más larga no significa que sea una versión mayor. ('1' debería ser lo mismo que '1.0' y '1.0.0.0'.)
Compara números, no cadenas. ('3'<'21' debería ser verdadero. No falso.)
No pierdas tiempo en comparaciones inútiles dentro del bucle. (Comparando con ==)
Puedes elegir tu propio comparador.
Contras:
- No maneja letras en la cadena de versión. (¿Ni siquiera 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
