Comment comparer les versions dans Ruby?


120

Comment écrire un morceau de code pour comparer certaines chaînes de versions et obtenir les dernières?

Par exemple des chaînes comme: '0.1', '0.2.1', '0.44'.


J'avais besoin de comparer les contraintes de version pessimistes il y a quelque temps, mais je ne voulais pas dépendre de RubyGems pour le faire, j'ai donc écrit une Versionclasse simple qui fait tout ce dont j'ai besoin: shorts.jeffkreeftmeijer.com/2014
...

Réponses:


232
Gem::Version.new('0.4.1') > Gem::Version.new('0.10.1')

14
La Gem::Version...syntaxe m'a fait penser que j'aurais besoin d'installer une gemme. Mais ce n'était pas obligatoire.
Guillaume

Remarque: Cela donne une erreur sur la variable non définie 'Gem' pour moi sur Ruby 1.x, mais fonctionne comme prévu sur Ruby 2.x. Dans mon cas, je vérifiais que RUBY_VERSION n'était pas Ruby 1.x (pas 2.x), alors j'ai juste fait RUBY_VERSION.split ('.') [0] == "1" comme John Hyland et DigitalRoss le font.
uliwitness

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

6
@uliwitness ce n'est pas Ruby 1.x vs 2.x; c'est 1.8.x contre 1.9+. Ruby jusqu'à 1.8.x n'inclut pas les rubygems par défaut; vous avez besoin d'un require 'rubygems'pour accéder à l' Gemespace de noms. À partir de la version 1.9, cependant, il est automatiquement inclus.
Mark Reed

Cela a également fonctionné pour comparer les versions NPM génériques. +1
deepelement


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
Comme certaines des autres réponses ici, il semble que vous fassiez des comparaisons de chaînes au lieu de nombres, ce qui posera des problèmes lors de la comparaison de versions telles que «0.10» et «0.4».
John Hyland du

7
Évalué pour une solution concise qui ne nécessite pas l'installation d'un bijou.
JD.

2
Pour ce que ça vaut: le 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 code le rend moche, mais en gros, utiliser cette vs Gem :: Version est environ deux fois plus rapide.
Shai

Une version n'est cependant pas un tableau.
Sergio Tulentsev

15

Vous pouvez utiliser la Versionomygemme (disponible sur 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
J'ai vu cela, mais m'oblige à utiliser 2 gemmes pour faire une chose vraiment simple. Je veux utiliser cela comme dernier choix.
user239895

8
"Ne réinventez pas la roue". Parce que c'est simple ne veut pas dire que le programmeur n'y a pas travaillé et réfléchi. Utilisez la gemme, lisez le code et apprenez-en - et passez à des choses plus grandes et meilleures!
Trevoke

La gestion des dépendances et la maintenance des versions est un problème difficile, probablement beaucoup plus difficile que la tâche de comparer 2 versions. Je suis tout à fait d'accord pour dire que l'introduction de 2 dépendances supplémentaires devrait être un dernier recours dans ce cas.
kkodev

10

je ferais

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

Alors tu peux faire

a1 <=> a2

(et probablement toutes les autres comparaisons «habituelles»).

... et si vous voulez un test <ou >, vous pouvez faire par exemple

(a1 <=> a2) < 0

ou faites encore plus de fonctions d'emballage si vous en avez envie.


1
Array.class_eval {include Comparable} fera que tous les tableaux répondent à <,>, etc. Ou, si vous voulez juste faire cela à certains tableaux: a = [1, 2]; a.extend (Comparable)
Wayne Conrad

4
Le problème que j'ai trouvé avec cette solution est qu'elle renvoie que "1.2.0" est plus grand que "1.2"
Maria S

9

Gem::Version est le moyen le plus simple d'y aller:

%w<0.1 0.2.1 0.44>.map {|v| Gem::Version.new v}.max.to_s
=> "0.44"

Bien mieux que la versionomy qui nécessite une extension c !?
W.Andrew Loe III

Je ne pense pas que «max» fonctionnera .. il indiquera que 0,5 est supérieur à 0,44. Ce qui n'est pas vrai lors de la comparaison des versions semver.
Flo Woo

2
cela semble avoir été corrigé dans la dernière Gem :: Version. 0,44 est correctement signalé comme supérieur à 0,5 maintenant.
Flo Woo

5

Si vous voulez le faire à la main sans utiliser de gemmes, quelque chose comme ce qui suit devrait fonctionner, même si cela semble un peu perly.

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

Essentiellement, vous transformez chaque chaîne de version en un tableau d'entiers, puis utilisez l' opérateur de comparaison de tableau . Vous pouvez décomposer les étapes des composants pour obtenir quelque chose d'un peu plus facile à suivre si cela se passe dans le code que quelqu'un devra maintenir.


-1

J'ai eu le même problème, je voulais un comparateur de version sans Gem, j'ai proposé ceci:

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.