2010-01-12 4 views
97

Comment écrire un morceau de code pour comparer certaines versions de chaînes et obtenir le plus récent? Par exemple, des chaînes comme '0.1', '0.2.1', '0.44'.Comment comparer des versions dans Ruby?

+0

Bonne question !! – Robert

+0

J'avais besoin de comparer des contraintes de version pessimistes il y a quelques temps, mais je ne voulais pas m'en remettre à RubyGems, alors j'ai écrit une simple classe 'Version' qui fait tout ce dont j'ai besoin: http://shorts.jeffkreeftmeijer.com/2014/compare-version-numbers-with-pessimistic-constraints/ – jkreeftmeijer

Répondre

15

Vous pouvez utiliser le Versionomy gem (disponible à 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 
+3

J'ai vu ça, mais il me faut utiliser 2 gemmes pour faire une chose vraiment simple. Je veux l'utiliser comme dernier choix. – user239895

+8

"Ne pas réinventer la roue". Parce que c'est simple ne signifie pas que le programmeur n'a pas mis du travail et y a réfléchi. Utilisez la gemme, lisez le code et apprenez-en - et passez à des choses plus grandes et meilleures! – Trevoke

+0

La gestion des dépendances et la maintenance des versions est un problème difficile, probablement beaucoup plus difficile que la comparaison de 2 versions. Je suis entièrement d'accord que l'introduction de deux dépendances supplémentaires devrait être un dernier recours dans ce cas. – kkodev

9

Je ferais

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

Ensuite, vous pouvez faire

a1 <=> a2 

(et probablement tous les autres comparaisons "habituelles").

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

(a1 <=> a2) < 0 

ou de faire un peu plus d'emballage de fonction si vous êtes si incliné.

+1

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

+0

Cool, merci!(15 caractères) –

+3

Le problème que j'ai trouvé avec cette solution est qu'il retourne que "1.2.0" est plus grand que "1.2" –

3

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

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

Essentiellement, vous tournez chaque chaîne de version pour un tableau d'entiers, puis utilisez la array comparison operator. Vous pouvez diviser les étapes du composant pour obtenir quelque chose d'un peu plus facile à suivre si cela va dans le code que quelqu'un devra maintenir.

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 d'autres réponses ici, il semble que vous faites des comparaisons de chaînes au lieu de numériques, ce qui causera des problèmes lors de la comparaison de versions comme '0.10' et '0.4'. –

+1

Bon point; fixé maintenant. – DigitalRoss

+7

Mis à jour pour une solution concise qui ne nécessite pas l'installation d'une gemme. –

189
Gem::Version.new('0.4.1') > Gem::Version.new('0.10.1') 
+11

La syntaxe 'Gem :: Version ...' m'a fait penser que je devrais installer une gemme. Mais ce n'était pas nécessaire. – Guillaume

+0

Note: 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 RUBY_VERSION contre être Ruby 1.x (pas 2.x), donc 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'). Correspond? (Nil, '1.4.6beta4')' – levinalex

7

Gem::Version est le moyen facile d'aller ici:

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

Beaucoup mieux que la versionomie qui nécessite une c-extension !? –

+0

Je ne pense pas que 'max' fonctionnera .. il indiquera 0.5 pour être supérieur à 0.44. Ce qui n'est pas vrai lorsqu'on compare les versions semver. –

+2

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

30

Si vous devez vérifier pessimistic version constraints, vous pouvez utiliser Gem::Dependency comme ceci:

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

Les versions les plus récentes semblent nécessiter une chaîne pour le nom. Une chaîne vide fonctionne bien, c'est-à-dire 'Gem :: Dependency.new ('', '~> 1.4.5'). Match? ('', '1.4.6beta4')' –

+0

merci. mis à jour la réponse. – levinalex

-1

J'ai eu la même problème, je voulais un comparateur de version sans Gem, est venu avec 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 
Questions connexes