Como comparar versões no Ruby?

119

Como escrever um pedaço de código para comparar algumas versões de strings e obter as mais recentes?

Por exemplo cordas gosto: '0.1', '0.2.1', '0.44'.

user239895
fonte
Eu precisava comparar restrições de versão pessimistas há um tempo, mas não queria depender do RubyGems para fazê-lo, então escrevi uma Versionclasse simples que faz tudo o que preciso: shorts.jeffkreeftmeijer.com/2014/…
jkreeftmeijer

Respostas:

230
Gem::Version.new('0.4.1') > Gem::Version.new('0.10.1')
mais grosseiro
fonte
14
A Gem::Version...sintaxe me fez pensar que precisaria instalar uma gema. Mas não foi necessário.
Guillaume
Nota: Isso gera um erro sobre a variável indefinida 'Gem' para mim no Ruby 1.x, mas funciona conforme o esperado no Ruby 2.x. No meu caso, eu estava verificando RUBY_VERSION como Ruby 1.x (não 2.x), então fiz RUBY_VERSION.split ('.') [0] == "1", como John Hyland e DigitalRoss fazem.
uliwitness
5
Gem::Dependency.new(nil, '~> 1.4.5').match?(nil, '1.4.6beta4')
levinalex
6
@uliwitness não é Ruby 1.x vs 2.x; é 1.8.x vs 1.9+. Ruby através da 1.8.x não inclui rubygems por padrão; você precisa de um require 'rubygems'para obter acesso ao Gemespaço para nome. A partir de 1.9, no entanto, é incluído automaticamente.
Mark Reed
Isso também funcionou para comparar versões NPM curinga. +1
deepelement
35

Se você precisar verificar restrições de versão pessimistas , poderá usar Gem :: Dependency como este:

Gem::Dependency.new('', '~> 1.4.5').match?('', '1.4.6beta4')
levinalex
fonte
1
Versões mais recentes parecem exigir uma sequência para o nome. Uma cadeia vazia funciona bem, isto éGem::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
fonte
3
Como algumas das outras respostas aqui, parece que você está fazendo comparações de strings em vez de numéricas, o que causará problemas ao comparar versões como '0,10' e '0,4'.
John Hyland
7
Voto positivo para solução concisa que não requer a instalação de uma jóia.
JD.
2
Pelo que vale a pena: o 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 blob de código está tornando-o feio, mas basicamente, o uso deste vs Gem :: Version é duas vezes mais rápido.
Shai 29/10
Uma versão não é uma matriz, no entanto.
Sergio Tulentsev
15

Você pode usar a Versionomygema (disponível no 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
fonte
4
Eu já vi isso, mas preciso que eu use 2 gemas para fazer uma coisa realmente simples. Eu quero usar isso como última escolha.
precisa saber é o seguinte
8
"Não reinvente a roda". Porque é simples, não significa que o programador não tenha trabalhado e pensado nele. Use a gema, leia o código e aprenda com ela - e vá para coisas maiores e melhores!
Trevoke 13/01/10
O gerenciamento de dependências e a manutenção da versão é um problema difícil, provavelmente muito mais difícil do que a tarefa de comparar duas versões. Concordo totalmente que a introdução de mais 2 dependências deve ser o último recurso nesse caso.
22417 kkodev
10

eu faria

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

Então você pode fazer

a1 <=> a2

(e provavelmente todas as outras comparações "usuais").

... e se você quer um <ou >teste, você pode fazer por exemplo,

(a1 <=> a2) < 0

ou faça mais algumas funções, se você quiser.

Carl Smotricz
fonte
1
Array.class_eval {include Comparable} fará com que todas as matrizes respondam a <,>, etc. Ou, se você quiser fazer isso com determinadas matrizes: a = [1, 2]; a.extend (Comparável)
Wayne Conrad
4
O problema que eu encontrei com esta solução é que ela retorna que "1.2.0" é maior que "1.2"
Maria S
9

Gem::Version é a maneira mais fácil de ir aqui:

%w<0.1 0.2.1 0.44>.map {|v| Gem::Version.new v}.max.to_s
=> "0.44"
Mark Reed
fonte
Muito melhor do que a versão que requer uma extensão c !?
W. Andrew Loe III
Eu não acho que 'max' irá funcionar .. relatará 0,5 para ser maior que 0,44. O que não é verdade quando se compara versões semver.
Flo Woo
2
isso parece ter sido corrigido na versão mais recente do Gem :: Version. 0,44 é relatado corretamente como superior a 0,5 agora.
precisa saber é o seguinte
5

Se você quiser fazer isso manualmente, sem usar pedras preciosas, algo como o seguinte deve funcionar, embora pareça um pouco complicado.

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

Essencialmente, você transforma cada string de versão em uma matriz de números inteiros e usa o operador de comparação de matrizes . Você pode interromper as etapas do componente para obter algo um pouco mais fácil de seguir se isso estiver ocorrendo no código que alguém precisará manter.

John Hyland
fonte
-1

Eu tinha o mesmo problema, queria um comparador de versões sem gemas, e vi o seguinte:

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
fonte