each, times and for
At closing, Bean Cart prints one line for every order in today's list, and at opening it rinses the milk jug exactly 3 times. Neither needs a condition: you already know what to go through. Ruby programmers almost never use a counter variable for this. They ask the collection, or the number, to do the looping.
The rule
Ask the collection to loop:
eachfor items,timesfor counts.
each hands every element of an array, a hash or a range to a block, one at a time. The name between the bars is the block parameter:
orders = ["latte", "tea", "mocha"]
orders.each do |drink|
puts "Make a #{drink}"
end
orders.each { |drink| puts drink } # braces for one-linersdo ... end and { } both create blocks. The convention is braces for one line, do ... end for several.
A hash gives each pair to the block, which usually takes two parameters:
menu = { latte: 4.50, tea: 2.50 }
menu.each do |drink, price|
puts "#{drink}: #{price}"
endWhen you also need the position, use each_with_index, which passes the item first and the index second:
orders.each_with_index { |drink, i| puts "#{i + 1}. #{drink}" }
# 1. latte 2. tea 3. mochatimes is a method on integers that runs the block that many times, counting from 0:
3.times { |n| puts "Rinse #{n}" } # Rinse 0, Rinse 1, Rinse 2For counting between other numbers, integers also offer 1.upto(5) and 5.downto(1), and ranges have each too: (1..5).each.
Ruby does have a for loop:
for drink in orders
puts drink
endIt works, but it is rarely used, for a subtle reason. A for loop does not create its own scope, so drink still exists after the loop ends, holding the last item. A block parameter in each disappears when the block finishes, which keeps the rest of your code tidy. Style guides therefore prefer each.
The shortcut
Ask "What am I going through?" A collection: call each on it. A number of repetitions: call times on the number. Need the position too: each_with_index. Reach for while only when there is no collection and no count, just a condition.
What am I going through?
The
ordersarray, and I need line numbers. Soeach_with_index.
Drills
Answer fast — trust the rule. The first three drills use these loops:
4.times { |n| print n } # loop A
menu = { latte: 4.50 }
menu.each { |a, b| puts a } # loop B
orders.each_with_index { |x, y| puts y } # loop CWhat does loop A print?
In loop B, what is a?
In loop C, which parameter holds the index?
After for drink in ["latte", "tea"]; end, what is drink?
Which is the most idiomatic way to print each order?
Remember
- Ask the collection to loop:
eachfor items,timesfor counts,each_with_indexfor both. - "What am I going through?" decides the method;
whileis for plain conditions. - Trap:
forleaks its variable after the loop, andtimesstarts at 0.
