> mthadley_

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!