The Pragmatic Studio

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.

  1. First, create a new cards.rb file which defines the following array of cards:

    cards = ["Jack", "Queen", "King", "Ace", "Joker"]
    
  2. 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
  3. 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!

  4. 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 the Array class. Calling the shuffle method on an array returns a new array with the elements (you guessed it) shuffled.

    You ran it multiple times, right?

  5. 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 called each. Give it a try!

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

  1. 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
  2. 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
  3. 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

  1. Methods such as each that invoke a block of code repeatedly are called ________.

  2. The each method iterates through each element in a collection and assigns each element in turn to the ________.

  3. 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
    
  4. In the example above, how many times is the code block run?

  5. What's the output of the code above?

  6. 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.
  7. 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
    
  8. 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.

  1. 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 }
    
  2. 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.

All course material, including videos and source code, is copyrighted and licensed for individual use only. You may make copies for your own personal use (e.g. on your laptop, on your iPad, on your backup drive). However, you may not transfer ownership or share the material with other people. We make no guarantees that the source code is fit for any purpose. Course material may not be used to create training material, courses, books, and the like. Please support us by encouraging others to purchase their own copies. Thank you!

Copyright © 2005–2024, The Pragmatic Studio. All Rights Reserved.