How do I sort a list of versions in Ruby? I've seen stuff about natural sort, but this is a step beyond that.
Input is a bunch of strings like this:
input = ['10.0.0b12', '10.0.0b3', '10.0.0a2', '9.0.10', '9.0.3']
I can almost do it with the naturally gem:
require 'naturally'
Naturally.sort(input)
=> ["9.0.3", "9.0.10", "10.0.0a2", "10.0.0b12", "10.0.0b3"]
Problem: 10.0.0b3 is sorted after 10.0.0b12; 10.0.0b3 should be first.
Anyone have a way that works? Other languages are helpful too!
The Ruby sort method works by comparing elements of a collection using their <=> operator (more about that in a second), using the quicksort algorithm. You can also pass it an optional block if you want to do some custom sorting. The block receives two parameters for you to specify how they should be compared.
You can use the sort method on an array, hash, or another Enumerable object & you'll get the default sorting behavior (sort based on <=> operator) You can use sort with a block, and two block arguments, to define how one object is different than another (block should return 1, 0, or -1)
Ruby ships with the Gem class, which knows about versions:
ar = ['10.0.0b12', '10.0.0b3', '10.0.0a2', '9.0.10', '9.0.3']
p ar.sort_by { |v| Gem::Version.new(v) }
# => ["9.0.3", "9.0.10", "10.0.0a2", "10.0.0b3", "10.0.0b12"]
If you interpret this as "sort by each segment of digits", then you the following will handle your example input above:
input.map{ |ver| ver.split(%r{[^\d]+}).map(&:to_i) }.zip(input).sort.map(&:last)
=> ["9_0", "9_1", "10_0b3", "10_0b12"]
That is,
10_0b3
["10","0","3"]
[10,0,3]
[[[10, 0, 12], "10_0b12"], [[10, 0, 3], "10_0b3"], [[9, 0], "9_0"], [[9, 1], "9_1"]]
[10,0,3] < [10,0,12]
Now granted, this is still quite custom -- version numbers as simple as "9_0a" vs "9_0b" won't be handled, both will appear to be [9,0] -- so you may need to tweak it further, but hopefully this starts you down a viable path.
EDIT: Example input above changed, so I changed the regex to make sure the digit-matching is greedy, and with that it still holds up:
irb(main):018:0> input = ['10.0.0b12', '10.0.0b3', '9.0.10', '9.0.3']
=> ["10.0.0b12", "10.0.0b3", "9.0.10", "9.0.3"]
irb(main):025:0> input.map{ |ver| ver.split(%r{[^\d]+}).map(&:to_i) }.zip(input).sort.map(&:last)
=> ["9.0.3", "9.0.10", "10.0.0b3", "10.0.0b12"]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With