Як порівняти версії в 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.