By the end of this, students should be able to:
- Use iteration to perform operations on collections
- Choose the right iteration operation
- Describe difference in types of iteration methods
- Predict the result of iterating over a block
each
takes a collection, and repeats over each element in the collection, making the element available to a block. Its returns value is the original collection
In the following example, we will use a range of 1..10
and print the number doubled.
(1..10).each do |number|
puts number * 2
end
The return result of this is the original range of 1..10
. That doesn't seem terribly useful does it?
We also know that we are avoiding the use of puts
in this class! What if we wanted to return the results from a function? Well, we can use an array to store values. Let's try that:
temp_array = []
(1..10).each do |number|
temp_array << number * 2
end
temp_array
Awesome, now the last line of temp_array
will return the value of an array of all the numbers doubled.
But we've had to mutate the data. Maybe that's ok, but it also encourages me to do several things within the block of the each
method, and perhaps not focus on just a single task. For example:
(1..10).each do |number|
puts number * 2
# Well, we're already here, let's get more done!
# Go to Store
# Ask about the news
# Get a haircut
# Return value of block doesn't matter...
end
This is all over the place. Hard to test. Doesn't focus well. Hmm. Seems sub-optimal. Let's come back to this.
map
is very much like each, in that it loops over a collection and makes each element available to the block as a local variable. But it returns the value of each block as a new array! That temporary array we made before isn't needed at all. In fact, if the map
is the last thing in a function we don't even have to assign the line to local variable.
I've wrapped the code below in a method definition, just so you can see this in action!
def double_elements_with_map(collection)
collection.map do |number|
number * 2
end
end
So if you want to loop over a collection, and so something with each element, and then have a set made up of these altered elements as the result, map
is the perfect tool. This is a much better pattern
-
each
doesn't really return anything useful to us as a value. -
map
returns the result of each block (the last line) as a new array without altering the original collection. -
each
encourages mutation of data, and lets people get in the habit of doing multiple things in the block since the return value of the block doesn't matter. -
map
makes you laser focus on one task, and discourages mutation of data. This will lead to fewer bugs in the long term. -
each
is harder to test, since you might be doing multiple things in it, which probably have side-effects. -
map
is really easy to test, making coding quicker and fewer bugs -
each
is boring, but overused. Only really use it for outputting Rails Views next week. -
map
has some neat tricks, and is a common pattern in functional programming
Let's say we wanted to take an array of strings, and reverse each element.
We could do this with a temporary array and each:
temp_array = []
['beep', 'boop', 'buzz'].each do |element|
temp_array << element.reverse
end
temp_array
But this is too long, so let's do it with map.
['beep', 'boop', 'buzz'].map do |element|
element.reverse
end
We can do it even better and shorter. We can do this nifty symbol to proc trick, which allows us to tell the name of a method that we'd like to call on every element in the collection.
['beep', 'boop', 'buzz'].map(&:reverse)
This is a little weird to understand, but essentially we can use the (&:reverse)
instead of writing out an entire block to just say 'run this method on everything in the collection'
You can also do this with other methods that take a block
What if we wanted to do more than one thing to the set? Perhaps uppercase and also reverse the elements?
['beep', 'boop', 'buzz'].map(&:upcase).map(&:reverse)
What would have been a very long and hard to test method with each
, becomes a ninja-level one-liner by using map
. There's also other really cool methods like reduce
that can be chained with map
. The power of these cannot be understated.
We won't be using while
frequently, so we don't go into it in great depth.
while
will loop over a block until the conditional expression is false, due to some mutation of a variable inside the block. This requires mutation of a variable and can also get you stuck inside an infinite loop if the condition is never set to false.
It can be useful in game programming for the main game loop, but less so in web applications. Due to its mutation and potentially infinite state, it is more difficult to test from a TDD standpoint. A brief example however:
x = 0
while(x < 10) do
x = x + 2
puts x
end
Question: How many times will the above loop execute, and what will the last value output be?
times
is an occasionally used method on Fixnum, which can be used to repeat a block of code. It can optionally pass through how many times it has been done as a local variable, which starts at 0.
For example:
5.times do |time|
puts "#{time} time!"
end
Question: Why does the above example start with a 0 as the output of the string?
Look a few days forward at the Enumerable Exercise that we'll be doing Friday.
There are many control structures that you can use in Ruby, but simply aren't idiomatic. When we get to Rails, we will almost never use while
. for
loops are possible in Ruby, but aren't idiomatic and you'll never see them. Case statements are almost never used in Rails.
You will frequently see each
when rendering Rails View templates. each
is over-used in other contexts however, and more often than not you actually want to use map
or reduce
. When possible, favor use of map
over each
.
List additional related resources such as videos, blog posts and official documentation.