.

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.

  1. ("A".."C").map #=> ["A","B","C"]
  2. ("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)

  1. "Y" <=> "Z"  #=> -1
  2. "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.

  1. class LetterSeries
  2.   include Comparable
  3.   attr_accessor :value
  4.  
  5.   def initialize(n)
  6.     @value = n
  7.   end
  8.  
  9.   def self.from_i(n)
  10.     b = "A"
  11.     (n-1).times {b.succ!}
  12.     LetterSeries.new(b)
  13.   end
  14.  
  15.   def to_i
  16.     (0…@value.size).map.inject(0)
  17.         {|sum,n| sum + (@value[-1-n]-64) * 26**(n)}
  18.   end
  19.  
  20.   def <=>(other)
  21.     self.to_i <=> other.to_i
  22.   end
  23.  
  24.   def succ
  25.     LetterSeries.new(@value.succ)
  26.   end
  27.  
  28.   def to_s
  29.     @value
  30.   end
  31.  
  32.   def +(n)
  33.     LetterSeries.from_i(self.to_i + n)
  34.   end
  35.  
  36.   def -(n)
  37.     LetterSeries.from_i(self.to_i – n)
  38.   end
  39.    
  40. end

Now some tests….

  1. z = LetterSeries.new("Z")
  2. ac = LetterSeries.new("AC")
  3. (z..ac).map {|n| n.to_s} #=> ["Z","AA","AB","AC"]
  4. (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.

No comments yet. Be the first.

Leave a reply