Ranges in Ruby
Ranges in Ruby are just great. If you’ve never seen one, they are most
commonly used with the ..
or ...
operators, which have a “start” and “end”
on either side. For starters, they simplify the common “bounds checking” you
would otherwise do with multiple comparison operators. For example:
# Instead of using `>`, `&&`, and `<`, use a Range!
def high_school?(grade)
(9..12).include?(grade)
end
high_school?(8) # => false
high_school?(9) # => true
The Range
class has a bunch of other useful methods, and you get even more
since it includes Enumerable
. Plus, it works with any object that implements
<=>
:
# Some stuff you get from `Enumerable`:
(10..20).filter(&:odd?) # => [11, 13, 15, 17, 19]
(1..100).sum # => 5050
# Strings also work:
letters = ('A'..'Z').to_a.join # => "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
# So does `Time`:
month_start = Time.new(2023, 1)
month_end = Time.new(2023, 2)
now = Time.now # => 2023-01-14 15:42:08.38739 -0800
(month_start...month_end).include?(now) # => true
# You can simplify this even further by using `Comparable#between?`:
now.between?(month_start, month_end) # => true
I really like how ranges in Ruby are integrated with the rest of the language and standard library. Here’s a grab-bag of examples:
# `Range` implements `#===`, so it works with `case` expressions...
def letter_grade(grade)
case grade
when ...60 then "F"
when 60...70 then "D"
when 70...80 then "C"
when 80...90 then "B"
when 90.. then "A"
end
end
letter_grade(86) # => "B"
letter_grade(55) # => "F"
# Or with good ol' `Enumerable#grep`:
passing_scores = [54, 61, 75, 95].grep(65..) # => [75, 95]
# Slicing arrays (or strings):
%w[one two three four five][1..3] # => ["two", "three", "four"]
# Roll a d20:
rand(1..20) # => 14
Their usefulness extends to many third-party gems. For example, when querying databases using Sequel or Active Record.
I’m sure there’s a lot more you can do with ranges. I could see them being a good representation for version constraints, say if you were writing a package manager. Does Bundler use ranges internally for anything? Maybe I’ll look into that some time.
…end
Even if you aren’t using Ruby, other languages have adopted first-class syntax for ranges, like Rust. So next time you’re writing some code with the concept of a “start” and “end”, see if a range can help simplify it!