In Progress
Unit 1, Lesson 1
In Progress

Downto

Video transcript & code

Here's a quickie for you. The other day I was working on a little coding kata, where the goal is to print out the verses to the drinking song "99 Bottles of Beer on the Wall". For the purpose of this demonstration, I'll narrow the problem down to just the first five verses.

Fresh off of making an episode about Ruby ranges, I automatically typed up some code that looked something like this.

(99..95).each do |n|
  puts "#{n} bottles of beer on the wall, #{n} bottles of beer"
  puts "Take one down, pass it around, #{n-1} bottles of beer on the wall"
end

I executed this code, confident I would get the result I expected. And then I was very surprised when nothing at all came out.

What I had forgotten was that Ruby ranges are one-way: they only go up, never down. When we expand this range to an array, it is empty:

(99..95).to_a                   # => []

To be perfectly frank, I consider this an omission in Ruby. Other languages have range features that can go either up or down. This is one of those rare cases in Ruby where typing out the code we imagine should work results in a failure.

It is very rare to see traditional C-style for loops in Ruby code, and it would be jarring if we had to resort to one now. Fortunately, Ruby doesn't leave us completely out in the cold in this situation. Instead of a Range, we can use the #downto method. We send it to the starting integer, and provide the ending number as an argument. Our block will now be called with the number 99, then 98, then 97, 96, and finally 95.

99.downto(95) do |n|
  puts "#{n} bottles of beer on the wall, #{n} bottles of beer"
  puts "Take one down, pass it around, #{n-1} bottles of beer on the wall"
end

# >> 99 bottles of beer on the wall, 99 bottles of beer
# >> Take one down, pass it around, 98 bottles of beer on the wall
# >> 98 bottles of beer on the wall, 98 bottles of beer
# >> Take one down, pass it around, 97 bottles of beer on the wall
# >> 97 bottles of beer on the wall, 97 bottles of beer
# >> Take one down, pass it around, 96 bottles of beer on the wall
# >> 96 bottles of beer on the wall, 96 bottles of beer
# >> Take one down, pass it around, 95 bottles of beer on the wall
# >> 95 bottles of beer on the wall, 95 bottles of beer
# >> Take one down, pass it around, 94 bottles of beer on the wall

Like so many other iterative methods in Ruby, #downto can also return an Enumerator. If we omit the block, we can then chain on whatever Enumerable method we like, such as #map.

99.downto(95).map{ |n| "#{n} bottles of beer" }
# => ["99 bottles of beer",
#     "98 bottles of beer",
#     "97 bottles of beer",
#     "96 bottles of beer",
#     "95 bottles of beer"]

It probably goes without saying that the #downto method is mirrored by an #upto method, which does exactly what it sounds like.

1.upto(5) do |n|
  puts n
end

# >> 1
# >> 2
# >> 3
# >> 4
# >> 5

Sadly, neither of these methods accepts a step unit, so they are only helpful in cases where we want to count up or down in increments of one.

If we want to get fancier, such as using custom increments or generating infinite sequences, there is a way to do that. But that's a topic for the next episode. Until then: Happy hacking!

Responses