Як порівняти версії в Ruby?


119

Як написати фрагмент коду, щоб порівняти рядки рядків версій та отримати новітні?

Наприклад рядки на кшталт: '0.1', '0.2.1', '0.44'.


Мені потрібно було порівняти обмеження песимістичної версії, але я не хотів залежати від RubyGems, щоб це зробити, тому я написав простий Versionклас, який робить все, що потрібно: шорти.jeffkreeftmeijer.com/2014/…
jkreeftmeijer

Відповіді:


231
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", як це роблять Джон Гіланд та DigitalRoss.
uliwitness

5
Gem::Dependency.new(nil, '~> 1.4.5').match?(nil, '1.4.6beta4')
levinalex

6
@uliwitness це не Ruby 1.x проти 2.x; це 1.8.x проти 1.9+. Рубін через 1.8.x не включає рубігем за замовчуванням; вам потрібно require 'rubygems'отримати доступ до Gemпростору імен. Однак, починаючи з 1.9, він автоматично включається.
Марк Рід

Це також працювало для порівняння альтернативних версій NPM. +1
заглиблення

35

Якщо вам потрібно перевірити обмеження песимістичної версії , ви можете використовувати Gem :: Dependency так:

Gem::Dependency.new('', '~> 1.4.5').match?('', '1.4.6beta4')

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')]

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 Code blob робить це некрасивим, але в основному, використання цього vs 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

4
Я бачив це, але вимагаю, щоб я використовував 2 дорогоцінні камені, щоб зробити справді просту річ. Я хочу використати це як останній вибір.
user239895

8
"Не винаходити колесо". Оскільки це просто не означає, що програміст не вкладав у нього роботи та думки. Використовуйте дорогоцінний камінь, прочитайте код і вчіться з нього - і переходите до великих і кращих речей!
Тревок

Управління залежністю та підтриманням версій є складною проблемою, ймовірно, набагато складнішою, ніж завдання порівняння двох версій. Я повністю погоджуюся з тим, що введення ще 2-х залежностей у цьому випадку має бути крайнім засобом.
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"
Maria S

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.
Flo Woo

2
це, здається, було виправлено в останній версії Gem ::. 0,44 вірно відображається як 0,5.
Flo Woo

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.