1.10 Each

When we met the Array class, we noted that most of what we do as developers is manage lists of things — photos, likes, followers, reviews, listings, messages, rides, events, concerts, projects, etc etc etc — and Array is the data structure that we’ll most commonly use to contain these lists.

Therefore, the most common reason we’ll have to write loops is to visit each element in an Array and do something interesting with it — for example, display the element to the user with some formatting around it.

1.10.1 Iterating over arrays with Integer’s times method

Try transforming the words in an Array using what you’ve learned so far about loops:

Write a program that, given a list of words from the user, would take each word and print it in three forms:

  • Capitalized
  • Reversed
  • Upcased

For example, for the input:

apple banana orange

Your program should output the following:

"Apple"
"elppa"
"APPLE"
"Banana"
"ananab"
"BANANA"
"Orange"
"egnaro"
"ORANGE"

After you’ve got it working, examine the model solution here. You’ll see that I chose to use .times for this job.

  • On Line 6, we count the length of the array.
  • On Line 8, we use that length with the .times method to kick off a loop with the correct number of iterations.
  • Within the block, we use the block variable (which we named the_index) to access the correct element within the array.

BENP: need an exercise using indexing in a loop here, this method is important in Ruby Gym

Using .times to iterate over an Array is not bad at all, especially because .times’s block variable starts at 0, just like array indexing does. Using .times is certainly cleaner than using while, where we would have to create and increment a counter variable ourselves, and then write a condition to make sure that the loop stops after the correct number of iterations (the length of the array).

1.10.2 Array’s each method

But we can do even better than using Integer’s .times method to iterate over an Array. There’s a method that you can call directly on the Array itself called .each. Compare the code below to the model solution above and try to find the differences:

p "Enter at least 2 words, separated by spaces:"
user_words = gets.chomp.split
p "user_words:"
p user_words

user_words.each do |the_word|
  p the_word.capitalize
  p the_word.reverse
  p the_word.upcase
end

Click “Run” and verify that both programs do the same thing.

Nice! .each has two clear benefits over using .times:

  • We don’t need to count the length of the array; .each does it for us and will take care of looping for the correct number of iterations.

  • The block variable, rather than containing an integer that we can use to access the correct element, will contain the element itself.

    So now when we name the block variable, we should choose a name I like to name the variables that contain arrays plurally (e.g. photos), and block variables singularly (e.g. photo) to make it clear to myself which is which — the list itself versus one element within the list. Whatever you do, don’t name the block variable plurally — that’s very confusing when you come back to your code later and have to make sense of it. that reflects what each element in the list is.

    .each will, behind the scenes, pull the correct element out of the array before each iteration begins and assign it to that block variable.

    Then, we just use that variable directly, and we don’t have to worry about accessing the array with .at.

The hardest part, I think, is getting your head around the block variable; in this case, |the_word|. It takes some practice.

Try to remember that it’s just a name that we make up, and .each takes care of putting each element in that variable for us behind the scenes. I could have called it zebra if I wanted to; there’s nothing special about the name — in particular, it doesn’t have to match the name of the variable containing the array. Just try to pick something descriptive of an individual element in the list.

Open the GitPod .each project for this chapter and start with the exercise spell_word.rb:

LTI{Load assignment}(https://github.com/bpurinton-appdev/each-chapter/tree/bp-additions)[MV4dKHMwdAFhfRn752YW3TAY]{KBpPhe42o6wDRi35rWagKY4F}(20)[each_project]

For a GitPod refresher, see here.

When you’re done with the first one, work through even_word.rb

And finally, work through letter_count.rb

1.10.3 Sneak peek

Just a sneak peek as to why .each is so important to get comfortable with: soon, you’ll be embedding Ruby loops in your web applications to create dynamic, data-driven pages with code that looks something like this:

<% newsfeed_photos.each do |the_photo| %>
  <div class="card">
    <img src="<%= the_photo.image_source %>">

    <p>
      <%= the_photo.caption %>
    </p>
  </div>
<% end %>

Code like this is what drives the dozens of dynamic applications you interact with on a daily basis — we pull a list of records from a database table, then we loop over them, and then we format each one using some markup language (in this case HTML for the browser, but it could be XML for native apps, etc).

1.10.4 Conclusion

That’s it for .each and loops. It’s time to meet a very important data structure class that we will be seeing a lot: Hash.

Addendum: each_with_index

There are some rare cases when you are looping over an array and, within the block, you would like access to the element and its index. For example, maybe you want to print a line after every other element. You could fall back to .times in these scenarios, but there’s also another Array method that has your back: .each_with_index. It looks like this:

p "Enter at least 2 words, separated by spaces:"
user_words = gets.chomp.split
p "user_words:"
p user_words

user_words.each_with_index do |the_word, the_index|
  p the_word.capitalize
  p the_word.reverse
  p the_word.upcase

  if the_index.odd?
    p "=" * 20
  end
end

As you can see, some methods provide more than one block variable. .each_with_index allows you to name two variables within the pipes; the first one will receive the element, and the second one will receive the index of the iteration. Within the block you can use both variables as you see fit. In rare cases, handy.