Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Loops and Iteration  ›  Lesson

each, times and for

each 12 minRuby loops by asking objects to iterate: each goes through the items of a collection, times repeats a block a number of times counting from zero, and for works but leaks its variable.

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: each for items, times for 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-liners

do ... 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}"
end

When 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. mocha

times 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 2

For 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
end

It 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.

Printing Bean Cart's numbered receiptStep 1 of 4
  1. What am I going through?

    The orders array, and I need line numbers. So each_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 C
quick drill

What does loop A print?

quick drill

In loop B, what is a?

quick drill

In loop C, which parameter holds the index?

quick drill

After for drink in ["latte", "tea"]; end, what is drink?

quick drill

Which is the most idiomatic way to print each order?

Remember

  • Ask the collection to loop: each for items, times for counts, each_with_index for both.
  • "What am I going through?" decides the method; while is for plain conditions.
  • Trap: for leaks its variable after the loop, and times starts at 0.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainWhy each over for?

Print Bean Cart's orders as a numbered list starting at 1. Explain which method you use, how its block parameters work, and why Ruby style prefers it to a for loop.