tags:

views:

174

answers:

7

How to write a piece of code to compare some versions strings and get the newest? For example strings like: 0.1 0.2.1 0.44

+7  A: 

You can use the Versionomy gem (available at 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
I have seen that, but require me to use 2 gems to do a really simple thing. I want to use that as last choice.
"Don't reinvent the wheel". Because it's simple doesn't mean the programmer didn't put work and thought into it. Use the gem, read the code, and learn from it - and move on to bigger and better things!
Trevoke
+4  A: 

I would do

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

Then you can do

a1 <=> a2

(and probably all the other "usual" comparisons).

...and if you want a < or > test, you can do e.g.

(a1 <=> a2) < 0

or do some more function wrapping if you're so inclined.

Carl Smotricz
Array.class_eval {include Comparable} will make all arrays respond to <, >, etc. Or, if you just want to do this to certain arrays: a = [1, 2]; a.extend(Comparable)
Wayne Conrad
Cool, thanks! (15 chars)
Carl Smotricz
+2  A: 

You're looking for natural-order comparison.

Jonathan Feinberg
A: 

If you want to do it by hand without using any gems, something like the following should work, though it's a little perly looking.

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

Essentially, you turn each version string in to an array of integers and then use the array comparison operator. You could break out the component steps to get something a little easier to follow if this is going in code somebody will need to maintain.

John Hyland
A: 

It's very easy to compare arrays in ruby, assuming the native <=> comparison works for every element of the array. You could do something like this, if all your arrays were numerics separated by . (or using the natural-order comparison library mentioned above, to support version components like 1a25):

#!/usr/bin/ruby
versions = ["1.2", "1.2", "1.2.3", "0.1", "0.2.1", "0.44"]

# This def taken from http://david-burger.blogspot.com/2008/09/generating-combinations-in-ruby-and_21.html
def generate_combinations(array, r)
  n = array.length
  indices = (0...r).to_a
  final = (n - r...n).to_a
  while indices != final
    yield indices.map {|k| array[k]}
    i = r - 1
    while indices[i] == n - r + i
      i -= 1
    end
    indices[i] += 1
    (i + 1...r).each do |j|
      indices[j] = indices[i] + j - i
    end
  end
  yield indices.map {|k| array[k]}
end

# This is the real 'magic' - http://ruby-doc.org/core/classes/Array.html#M002204
def version_compare(first, second)
  return first.split('.') <=> second.split('.')
end

def format_comparison(first, second)
  compared = version_compare(first,second)
  relation = case compared
  when 0: "equal to"
  when 1: "greater than"
  when -1: "less than"
  end
  return "#{first} is #{relation} #{second}"
end  

generate_combinations(versions, 2) do |pair|
  puts format_comparison(pair[0], pair[1])
  puts format_comparison(pair[1], pair[0])
end
JdV
John Hyland
A: 
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
Like some of the other answers here, it looks like you're doing string comparisons instead of numerical, which will cause problems when comparing versions like '0.10' and '0.4'.
John Hyland
Good point; fixed now.
DigitalRoss
+1  A: 

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

grosser