Have you ever wondered how ranges work in Ruby?
Even if you haven’t, isn’t it fun to discover how things work under the hood?
That’s exactly what I’m going to show you in this post!
Just as a reminder, this is what a Ruby range looks like:
The parenthesis are not necessary to define a
Range, but if you want to call methods on your range you will need them (otherwise you are calling the method on the 2nd element of the range, instead of the range itself).
Range has some useful methods, like the
(10..20).step(2).to_a # [10, 12, 14, 16, 18, 20]
Range methods to be aware of are:
include?. It would be a mistake to think that they do the same thing, because they don’t.
include? method just does what you would expect, check for inclusion inside the range. So it would be equivalent to expanding the
Range into an
Array and checking if something is in there.
cover? is different, all it does is check against the initial & ending values of the range (
begin <= obj <= end), which can yield unexpected results.
('a'..'z').include? "cc" # false ('a'..'z').cover? "cc" # true
cover? example is equivalent to:
"a" <= "cc" && "cc" <= "z"
The reason this returns
true is that strings are compared character by character. Since "a" comes before "c", the characters that come after the first "c" don't matter.
Ranges are not limited to numbers & letters, you can use any objects as long as they implement the following methods:
For example, here is a time range:
require 'time' t1 = DateTime.new t2 = DateTime.new + 30 next_30_days = t1..t2 # Example use next_30_days.select(&:friday?).map(&:day)
So how does this work? Let's take a look at this implementation:
def range(a, b) # if the first element is bigger than the second # then this isn't a sequential range return  if a > b out =  # advance until the 2nd element is the same # as the first one while a != b out << a a = a.next end # add last element (inclusive range) # this also returns the results via implicit return out << a end p range 1, 10 p range 'a', 'z'
I added some comments to help you understand what is going on. The idea is that we keep calling the next method on the first object until it is equal to the second one, the assumption is that they will eventually meet.
Most of the time you will be using number & character ranges, but it's still good to know how you can use ranges in a custom class.
class LetterMultiplier attr_reader :count include Comparable def initialize(letter, count) @letter = letter @count = count end def succ self.class.new(@letter, @count + 1) end def <=>(other) count <=> other.count end end a = LetterMultiplier.new('w', 2) b = LetterMultiplier.new('w', 8) # Print array with all the items in the range p Array(a..b)
The key here is to make sure that you implement the
succ methods correctly.
If you want to use the
include? method you need to include the
Comparable module, which adds methods like
> (all based on the results of the
In this article you have learned how ranges work in Ruby so you can understand them better & implement your own objects that support range operations.
Don't forget to subscribe to the newsletter below so you don't miss the next post 🙂