Как сравнивать версии в Ruby?

120

Как написать кусок кода, чтобы сравнить строки некоторых версий и получить самую новую?

Например строки вроде: '0.1', '0.2.1', '0.44'.

user239895
источник
Некоторое время назад мне нужно было сравнить пессимистичные ограничения версий, но я не хотел зависеть от RubyGems, поэтому я написал простой Versionкласс, который делает все, что мне нужно: shorts.jeffkreeftmeijer.com/2014/…
jkreeftmeijer

Ответы:

233
Gem::Version.new('0.4.1') > Gem::Version.new('0.10.1')
грубее
источник
14
Gem::Version...Синтаксис сделал мне казалось , что я должен был бы установить камень. Но этого не потребовалось.
Гийом
Примечание. Это дает мне ошибку о неопределенной переменной «Gem» в Ruby 1.x, но работает, как ожидалось, в Ruby 2.x. В моем случае я проверял Ruby_VERSION на соответствие Ruby 1.x (а не 2.x), поэтому я просто сделал RUBY_VERSION.split ('.') [0] == «1», как это делают John Hyland и DigitalRoss.
uliwitness
5
Gem::Dependency.new(nil, '~> 1.4.5').match?(nil, '1.4.6beta4')
levinalex 09
6
@uliwitness это не Ruby 1.x vs 2.x; это 1.8.x против 1.9+. Ruby до версии 1.8.x по умолчанию не включает rubygems; вам нужен, require 'rubygems'чтобы получить доступ к Gemпространству имен. Однако, начиная с 1.9, он включается автоматически.
Марк Рид,
Это также сработало для сравнения версий NPM с подстановочными знаками. +1
deepelement 07
35

Если вам нужно проверить ограничения пессимистичной версии , вы можете использовать Gem :: Dependency следующим образом:

Gem::Dependency.new('', '~> 1.4.5').match?('', '1.4.6beta4')
levinalex
источник
1
В новых версиях для имени требуется строка. Пустая строка работает нормально, то естьGem::Dependency.new('', '~> 1.4.5').match?('', '1.4.6beta4')
Питер Вагенет
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
источник
3
Как и некоторые другие ответы здесь, похоже, что вы выполняете сравнение строк вместо числового, что вызовет проблемы при сравнении версий, таких как '0.10' и '0.4'.
Джон Хайленд,
7
Проголосовали за краткое решение, не требующее установки драгоценного камня.
JD.
2
Чего стоит: 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 блоб кода делает его уродливым, но в основном использование этого по сравнению с Gem :: Version примерно в два раза быстрее.
Шай
Однако версия - это не массив.
Серджио Туленцев
15

Вы можете использовать Versionomyгем (доступен на 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
источник
4
Я видел это, но мне нужно использовать 2 камня, чтобы сделать действительно простую вещь. Я хочу использовать это как последний вариант.
user239895
8
«Не изобретайте велосипед». То, что это просто, не означает, что программист не вложил в это работу и не подумал. Используйте драгоценный камень, прочтите код и извлеките уроки из него - и переходите к большему и лучшему!
Trevoke
Управление зависимостями и поддержка версий - сложная проблема, вероятно, намного сложнее, чем задача сравнения двух версий. Я полностью согласен с тем, что введение еще двух зависимостей должно быть последним средством в этом случае.
kkodev
10

я бы сделал

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

Тогда ты можешь сделать

a1 <=> a2

(и, наверное, все остальные «обычные» сравнения).

... и если вы хотите <или >протестировать, вы можете сделать, например,

(a1 <=> a2) < 0

или сделайте еще несколько функций, если вам так хочется.

Карл Смотрич
источник
1
Array.class_eval {include Comparable} заставит все массивы реагировать на <,> и т.д. Или, если вы просто хотите сделать это с определенными массивами: a = [1, 2]; a.extend (сопоставимо)
Уэйн Конрад
4
Проблема, которую я обнаружил с этим решением, заключается в том, что оно возвращает, что «1.2.0» больше, чем «1.2»
Мария С.
9

Gem::Version это простой способ пойти сюда:

%w<0.1 0.2.1 0.44>.map {|v| Gem::Version.new v}.max.to_s
=> "0.44"
Марк Рид
источник
Намного лучше, чем версиями, для которых требуется c-расширение !?
W. Andrew Loe III
я не думаю, что "max" будет работать ... он сообщит, что 0,5 будет больше 0,44. Что неверно при сравнении версий semver.
Фло Ву
2
похоже, это было исправлено в последней версии Gem :: Version. 0,44 теперь правильно отображается как выше 0,5.
Фло Ву,
5

Если вы хотите сделать это вручную без использования драгоценных камней, то должно сработать что-то вроде следующего, хотя это немного на вид.

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

По сути, вы превращаете каждую строку версии в массив целых чисел, а затем используете оператор сравнения массивов . Вы можете разбить этапы работы с компонентами, чтобы сделать что-то более легкое для понимания, если это происходит в коде, который кому-то нужно будет поддерживать.

Джон Хайленд
источник
-1

У меня была та же проблема, я хотел компаратор версий без Gem, придумал следующее:

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
источник