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.
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:
For example, for the input:
apple banana orange
Your program should output the following:
"Apple"
"elppa"
"APPLE"
"Banana"
"ananab"
"BANANA"
"Orange"
"egnaro"
"ORANGE"
Click here for a REPL to try it.
After you’ve got it working, examine the model solution here. You’ll see that I chose to use .times
for this job.
.times
method to kick off a loop with the correct number of iterations.the_index
) to access the correct element within the array.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).
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 here for a REPL to try it.
Click “run” and verify that both programs do the same thing.
Nice! .each
has two clear benefits over using .times
:
.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 name1 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.
Just a sneak peek as to why we .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).
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
Click here for a REPL to try it.
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.
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. ↩