Each
Exercises
Overview
Methods such as each
that invoke a block of code repeatedly are often called iterators because they iterate over the objects in a collection. Something cool about Ruby is that it defines an each
method on built-in collection classes such as arrays and hashes.
Similar to the times
and upto
methods, the each
method calls an associated block once for each element in the collection. In the case of an array, it passes each array element to the block in turn. In the case of a hash, it passes each key/value pair to the block.
1... 2... 3... Let's start iterating!
1. Iterate Through Arrays and Hashes
We'll start by iterating through an array since you'll end up doing this 1.upto(infinity)
times in your career as a Ruby programmer.
-
First, create a new
cards.rb
file which defines the following array of cards:cards = ["Jack", "Queen", "King", "Ace", "Joker"]
-
Then use the
each
method to shout out (upcase
) the name of each card in turn, so you get the following output:JACK QUEEN KING ACE JOKER
-
A crazy dealer would also shout out the number of characters in each card name, like so:
JACK - 4 QUEEN - 5 KING - 4 ACE - 3 JOKER - 5
Go crazy by changing your block to format the output as shown above.
This illustrates that you can change your code block independent of the iterator. In other words, the loop is separate from what actually happens each time through the loop. That's one of the benefits of using iterators!
-
If you want to have a bit of fun, shuffle the cards before iterating through them. Ruby makes that easy with the
shuffle
method defined on theArray
class. Calling theshuffle
method on an array returns a new array with the elements (you guessed it) shuffled.You ran it multiple times, right?
-
OK, so now what if you wanted to iterate through the cards in the reverse order? Again, Ruby has you covered, this time with the
reverse_each
iterator method. That's right, not all iterator methods are calledeach
. Give it a try! -
Now let's suppose that the players of your card game got the following scores, represented in this Ruby hash:
scores = {"Larry" => 10, "Moe" => 8, "Curly" => 12}
Use the
each
method to print each player's name and score, like so:Larry scored a 10! Moe scored a 8! Curly scored a 12!
2. Iterate Through Frequent Flyers
Returning to our frequent flyer program (open your existing flyer.rb
file), we currently have an array of five flyers. Let's do something with them...
-
First, iterate through all the flyers and print out their name and miles flown, for example:
Flyer 1 - 1000 miles Flyer 2 - 2000 miles Flyer 3 - 3000 miles Flyer 4 - 4000 miles Flyer 5 - 5000 miles
-
Next, in a separate block, tally up the total miles flown by all the flyers and print out the grand total, like so:
Total miles flown: 15000
-
You know airlines are always trying to get you to switch your loyalty for bonus points. For each mile you fly, they'll give you additional bonus miles. Here's the current promotion for each airline, expressed in a hash:
promotions = { "United" => 1.5, "Delta" => 2.0, "Lufthansa" => 2.5 }
So, if you switch to Delta, you'll earn 2.0 times as many miles!
Iterate through the hash and print out each airline's promotion, so you get a sales pitch like this:
Earn 1.5x miles by flying United! Earn 2.0x miles by flying Delta! Earn 2.5x miles by flying Lufthansa!
Thank you for flying the friendly skies.™ Keep climbing!™ Nonstop you.™ :-)
Solution
The full solution for this exercise is in the
each
directory of the
code bundle.
Pop Quiz
-
Methods such as
each
that invoke a block of code repeatedly are called ________. -
The
each
method iterates through each element in a collection and assigns each element in turn to the ________. -
In the code below, ________ is the block parameter. Block parameters are ________ variables. They only live for the life of the block.
flavors = ['chocolate', 'vanilla', 'cinnamon'] flavors.each do |type| puts type.reverse end
-
In the example above, how many times is the code block run?
-
What's the output of the code above?
-
Given the following hash:
desserts = { "chocolate" => 1.00, "vanilla" => 0.75, "cinnamon" => 1.25 }
Write a block that produces the following output:
$2.0 for a cup of chocolate. $1.5 for a cup of vanilla. $2.5 for a cup of cinnamon.
-
In the code below, what's the output of
puts team
?team = "Broncos" 1.upto(4) do |number| team = "Bobcats" puts "#{number} Hooray #{team}" end puts team
-
In the code below (look carefully), what's the output of
puts team
?team = "Broncos" 1.upto(4) do |number; team| team = "Bobcats" puts "#{number} Hooray #{team}" end puts team
Bonus Round
Non-Obvious Iterators
We looked at using each
on arrays and hashes, which are obvious collections. But this pattern of calling each
with a block extends to other non-obvious collections, as well.
-
For example, a file is a collection of lines. So consider what the following code does:
file = File.open("poem.txt") file.each { |line| puts line }
-
Similarly, a directory is a collection of entries (files and other directories). It should come as no surprise what
each
does here:dir = Dir.new("/Users/mike") dir.each { |entry| puts entry }
Iterators Within Iterators
If you want to take things up to a higher altitude (sorry, we couldn't resist), combine your two iterators to print out the miles each flyer would earn by switching to each airline.
Here's the marketing pitch you're aiming for:
Flyer 1 could earn 1500.0 miles by flying United! Flyer 1 could earn 2000.0 miles by flying Delta! Flyer 1 could earn 2500.0 miles by flying Lufthansa! Flyer 2 could earn 3000.0 miles by flying United! Flyer 2 could earn 4000.0 miles by flying Delta! Flyer 2 could earn 5000.0 miles by flying Lufthansa! Flyer 3 could earn 4500.0 miles by flying United! Flyer 3 could earn 6000.0 miles by flying Delta! Flyer 3 could earn 7500.0 miles by flying Lufthansa! Flyer 4 could earn 6000.0 miles by flying United! Flyer 4 could earn 8000.0 miles by flying Delta! Flyer 4 could earn 10000.0 miles by flying Lufthansa! Flyer 5 could earn 7500.0 miles by flying United! Flyer 5 could earn 10000.0 miles by flying Delta! Flyer 5 could earn 12500.0 miles by flying Lufthansa!
Wrap Up
We've now used a number of iterator methods such as times
, upto
, and each
as a way of learning about blocks. Whenever you need to loop through the elements of a collection, use an iterator method and pass it a block.
Here's a visual overview of what we learned in this section:
We've barely scratched the surface of the methods in Ruby that take a block. Next up, we'll begin to explore the methods of the Enumerable
module and use blocks to select, reject, detect, and partition collections.