May 28, 2006
Ruby Series (”A”..”Z”)
One of the nice features of Ruby is the ‘range’ object that can represent a series of objects ranging from one object to another. A numerical series like [1, 2, 3, 4, 5, 6] can be represented as (1..6). This works well and is quite useful in many cases. Any object that implements a ‘<=>‘ operator and a ’succ’ method can be implemented as a range.
Ranges of letters don’t work as well.
-
("A".."C").map #=> ["A","B","C"]
-
("Z".."AB").map #=> []
What? Why doesn’t the second example work? That’s a valid series, it should return [ "Z", "AA", "AB" ].
The problem here seems to be that these strings don’t sort the way that ‘range’ expects them to. (Thanks to Robert Dober for pointing this out)
-
"Y" <=> "Z" #=> -1
-
"Z" <=> "AA" #=> 1
So one solution to this problem is to simply create a class that implements the correct semantics and sorts properly.
Here’s one implementation.
-
class LetterSeries
-
include Comparable
-
attr_accessor :value
-
-
def initialize(n)
-
@value = n
-
end
-
-
def self.from_i(n)
-
b = "A"
-
(n-1).times {b.succ!}
-
LetterSeries.new(b)
-
end
-
-
def to_i
-
(0…@value.size).map.inject(0)
-
{|sum,n| sum + (@value[-1-n]-64) * 26**(n)}
-
end
-
-
def <=>(other)
-
self.to_i <=> other.to_i
-
end
-
-
def succ
-
LetterSeries.new(@value.succ)
-
end
-
-
def to_s
-
@value
-
end
-
-
def +(n)
-
LetterSeries.from_i(self.to_i + n)
-
end
-
-
def -(n)
-
LetterSeries.from_i(self.to_i - n)
-
end
-
-
end
Now some tests….
-
z = LetterSeries.new("Z")
-
ac = LetterSeries.new("AC")
-
(z..ac).map {|n| n.to_s} #=> ["Z","AA","AB","AC"]
-
(z..z+3).map {|n| n.to_s| #=> ["Z","AA","AB","AC"]
Feel free to use this code if you find it handy.
See http://www.ruby-forum.com/topic/67300?reply_to=83726#83612 for the original discussion that inspired this code.
Filed by Kevin Olbrich at 9:42 pm under Ruby
No Comments
2 Comments