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'.

user239895
la source
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')
plus grossier
la source
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
35

Si vous avez besoin de vérifier les contraintes de version pessimistes , vous pouvez utiliser Gem :: Dependency comme ceci:

Gem::Dependency.new('', '~> 1.4.5').match?('', '1.4.6beta4')
levinalex
la source
1
Les versions plus récentes semblent exiger une chaîne pour le nom. Une chaîne vide fonctionne très bien, c'estGem::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
la source
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
notnoop
la source
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.

Carl Smotricz
la source
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"
Mark Reed
la source
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.

John Hyland
la source
-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
Wivlaro
la source