¿Cómo comparar versiones en Ruby?

119

¿Cómo escribir un fragmento de código para comparar algunas cadenas de versiones y obtener la más nueva?

Por ejemplo cadenas como: '0.1', '0.2.1', '0.44'.

user239895
fuente
Necesitaba comparar las restricciones de la versión pesimista hace un tiempo, pero no quería depender de RubyGems para hacerlo, así que escribí una Versionclase simple que hace todo lo que necesito: shorts.jeffkreeftmeijer.com/2014/…
jkreeftmeijer

Respuestas:

231
Gem::Version.new('0.4.1') > Gem::Version.new('0.10.1')
más grosero
fuente
14
La Gem::Version...sintaxis me hizo pensar que necesitaría instalar una gema. Pero no fue necesario.
Guillaume
Nota: Esto me da un error sobre la variable indefinida 'Gem' en Ruby 1.x, pero funciona como se esperaba en Ruby 2.x. En mi caso, estaba comprobando que RUBY_VERSION no fuera Ruby 1.x (no 2.x), así que hice RUBY_VERSION.split ('.') [0] == "1" como lo hacen John Hyland y DigitalRoss.
uliwitness
5
Gem::Dependency.new(nil, '~> 1.4.5').match?(nil, '1.4.6beta4')
levinalex
6
@uliwitness no es Ruby 1.x vs 2.x; es 1.8.x frente a 1.9+. Ruby hasta 1.8.x no incluye rubygems por defecto; necesita un require 'rubygems'para obtener acceso al Gemespacio de nombres. Sin embargo, a partir de la versión 1.9, se incluye automáticamente.
Mark Reed
Esto también funcionó para comparar versiones comodín de NPM. +1
profundización
35

Si necesita verificar las restricciones de versión pesimista , puede usar Gem :: Dependency de esta manera:

Gem::Dependency.new('', '~> 1.4.5').match?('', '1.4.6beta4')
levinalex
fuente
1
Las versiones más nuevas parecen requerir una cadena para el nombre. Una cadena vacía funciona bien, es decirGem::Dependency.new('', '~> 1.4.5').match?('', '1.4.6beta4')
Peter Wagenet
19
class Version < Array
  def initialize s
    super(s.split('.').map { |e| e.to_i })
  end
  def < x
    (self <=> x) < 0
  end
  def > x
    (self <=> x) > 0
  end
  def == x
    (self <=> x) == 0
  end
end
p [Version.new('1.2') < Version.new('1.2.1')]
p [Version.new('1.2') < Version.new('1.10.1')]
DigitalRoss
fuente
3
Al igual que algunas de las otras respuestas aquí, parece que está haciendo comparaciones de cadenas en lugar de numéricas, lo que causará problemas al comparar versiones como '0.10' y '0.4'.
John Hyland
7
Votado a favor por una solución concisa que no requiere la instalación de una gema.
JD.
2
Por lo que vale: vers = (1..3000000).map{|x| "0.0.#{x}"}; 'ok' puts Time.now; vers.map{|v| ComparableVersion.new(v) }.sort.first; puts Time.now # 24 seconds 2013-10-29 13:36:09 -0700 2013-10-29 13:36:33 -0700 => nil puts Time.now; vers.map{|v| Gem::Version.new(v) }.sort.first; puts Time.now # 41 seconds 2013-10-29 13:36:53 -0700 2013-10-29 13:37:34 -0700 Code blob lo está haciendo feo, pero básicamente, usar esto vs Gem :: Version es aproximadamente el doble de rápido.
Shai
Sin embargo, una versión no es una matriz.
Sergio Tulentsev
15

Puedes usar la Versionomygema (disponible en github ):

require 'versionomy'

v1 = Versionomy.parse('0.1')
v2 = Versionomy.parse('0.2.1')
v3 = Versionomy.parse('0.44')

v1 < v2  # => true
v2 < v3  # => true

v1 > v2  # => false
v2 > v3  # => false
notnoop
fuente
4
Lo he visto, pero requiere que use 2 gemas para hacer algo realmente simple. Quiero usar eso como última opción.
user239895
8
"No reinventes la rueda". El hecho de que sea simple no significa que el programador no haya trabajado ni pensado en ello. Usa la gema, lee el código y aprende de él, ¡y pasa a cosas más grandes y mejores!
Trevoke
La gestión de dependencias y el mantenimiento de versiones es un problema difícil, probablemente mucho más difícil que la tarea de comparar 2 versiones. Estoy totalmente de acuerdo en que introducir 2 dependencias más debería ser un último recurso en este caso.
kkodev
10

yo lo haría

a1 = v1.split('.').map{|s|s.to_i}
a2 = v2.split('.').map{|s|s.to_i}

Entonces puedes hacer

a1 <=> a2

(y probablemente todas las demás comparaciones "habituales").

... y si quieres una prueba <o >, puedes hacer, por ejemplo,

(a1 <=> a2) < 0

o hacer un envoltorio más funcional si así lo desea.

Carl Smotricz
fuente
1
Array.class_eval {include Comparable} hará que todas las matrices respondan a <,>, etc. O, si solo desea hacer esto con ciertas matrices: a = [1, 2]; a.extend (Comparable)
Wayne Conrad
4
El problema que encontré con esta solución es que devuelve que "1.2.0" es más grande que "1.2"
Maria S
9

Gem::Version es la forma más fácil de ir aquí:

%w<0.1 0.2.1 0.44>.map {|v| Gem::Version.new v}.max.to_s
=> "0.44"
Mark Reed
fuente
¿¡Mucho mejor que versionomy que requiere una extensión c !?
W. Andrew Loe III
No creo que 'max' funcione ... informará que 0,5 es mayor que 0,44. Lo cual no es cierto al comparar versiones de semver.
Flo Woo
2
esto parece haberse solucionado en la última versión de Gem ::. 0,44 se informa correctamente como superior a 0,5 ahora.
Flo Woo
5

Si desea hacerlo a mano sin usar gemas, algo como lo siguiente debería funcionar, aunque tiene un aspecto un poco perverso.

versions = [ '0.10', '0.2.1', '0.4' ]
versions.map{ |v| (v.split '.').collect(&:to_i) }.max.join '.'

Básicamente, convierte cada cadena de versión en una matriz de enteros y luego usa el operador de comparación de matriz . Puede dividir los pasos de los componentes para obtener algo un poco más fácil de seguir si esto va en el código que alguien necesitará mantener.

John Hyland
fuente
-1

Tuve el mismo problema, quería un comparador de versiones sin gemas, se me ocurrió esto:

def compare_versions(versionString1,versionString2)
    v1 = versionString1.split('.').collect(&:to_i)
    v2 = versionString2.split('.').collect(&:to_i)
    #pad with zeroes so they're the same length
    while v1.length < v2.length
        v1.push(0)
    end
    while v2.length < v1.length
        v2.push(0)
    end
    for pair in v1.zip(v2)
        diff = pair[0] - pair[1]
        return diff if diff != 0
    end
    return 0
end
Wivlaro
fuente