Ruby Arrays Part II - Iteration
Tags: ruby, tutorial
Published: 2020-07-03 19:09:06

<h1>What is Iteration?</h1>
Iteration is the repetition of a process. We've covered one form of iteration in an [earlier lesson](https://dev.to/bmweygant/ruby-loops-n27), but iteration also describes the manipulation of elements inside an array or hash. Fundamentally, there are 2 key differences between a `loop` and an `iteration`:

**1.** A `loop` will repeat an action a defined number of times, while an `iteration` works with a collection of data by allowing you to operate on each individual element. 
**2.** `Loops` *require* a user-defined ending somewhere in the method or they will break your program with an infinite loop. `Iterations` *could* have a user-defined end if desired, but will cease after the last element in the collection has been iterated over otherwise.

You will do this *thousands* of times as a developer, and array manipulation is one of the core skills you will need to grow as a programmer. Fortunately in Ruby, if you exclude the loop methods like `times` & `while`, there are only 2 primary iteration methods you must learn - `Each` & `Map`.

<h1>How Iterators Compare to Loops</h1>
So aside from the two differences stated above, maybe you don't see the benefit to iterators and want to continue using loops. The best way to demonstrate the benefits of iterators is a direct comparison demonstration. Spoiler alert: Iterators will save you ***a lot*** of typing in the long run.

The first one will use the `while` loop:
```ruby
nba_draft_top_10 = ["LaMelo Ball", "Onyeka Okongwu", "Anthony Edwards", "Killian Hayes", "James Wiseman", "Obi Toppin", "Tyrese Haliburton", "Aleksej Pokuseveski", "Cole Anthony", "Deni Avdija"]

i = 0
big_board = nba_draft_top_10.length

while i < big_board
    puts "#{nba_draft_top_10[i]} is one of the top rated prospects on my board."
    i+=1
end
#=>LaMelo Ball is one of the top rated prospects on my board.
#Onyeka Okongwu is one of the top rated prospects on my board.
#Anthony Edwards is one of the top rated prospects on my board.
#Killian Hayes is one of the top rated prospects on my board.
#James Wiseman is one of the top rated prospects on my board.
#Obi Toppin is one of the top rated prospects on my board.
#Tyrese Haliburton is one of the top rated prospects on my board.
#Aleksej Pokuseveski is one of the top rated prospects on my board.
#Cole Anthony is one of the top rated prospects on my board.
#Deni Avdija is one of the top rated prospects on my board.
```
Now lets see the same iteration using the `each` iterator:

```ruby
nba_draft_top_10 = ["LaMelo Ball", "Onyeka Okongwu", "Anthony Edwards", "Killian Hayes", "James Wiseman", "Obi Toppin", "Tyrese Haliburton", "Aleksej Pokuseveski", "Cole Anthony", "Deni Avdija"]

nba_draft_top_10.each do |prospect|
    puts "#{prospect} is one of the top rated prospects on my board."
end

#=>LaMelo Ball is one of the top rated prospects on my board.
#Onyeka Okongwu is one of the top rated prospects on my board.
#Anthony Edwards is one of the top rated prospects on my board.
#Killian Hayes is one of the top rated prospects on my board.
#James Wiseman is one of the top rated prospects on my board.
#Obi Toppin is one of the top rated prospects on my board.
#Tyrese Haliburton is one of the top rated prospects on my board.
#Aleksej Pokuseveski is one of the top rated prospects on my board.
#Cole Anthony is one of the top rated prospects on my board.
#Deni Avdija is one of the top rated prospects on my board.
```

What took us 6 lines of code with a `while` loop took *half* that with an `each` iterator. There are a few keys to how this happens:

**1.** Because iterators understand array `length` inherently, there is no need to set a variable to establish a numerical value to it (big_board).
**2.** Iterators don't need counter variables (i) or conditions (i < big_board) to keep track when to stop.
**3.** Because of #2, you also have no need to manually increment your counter (i+=1).
**4.** As a bonus, the block provided by the `each` iterator provides us a more accessible and readable variable (prospect) than the `while` loop did (nba_draft_top_10[i]).
 
Take note that syntactically `each` and `map` are similar and you could switch them in the above example. So now that we've demonstrated the power behind iterators, let us break down `each` and `map` some more.

<h1>Each and Each's Extended Family</h1>
`Each` is an iterator designed to iterate through your array without mutating the original array. Let's open up an `irb` session and see what that means:

First we define the array.

```
irb(main):001:0> nba_draft_top_10 = ["LaMelo Ball", "Onyeka Okongwu", "Anthony Edwards", "Killian Hayes", "James Wiseman", "Obi Toppin", "Tyrese Haliburton", "Aleksej Pokuseveski", "Cole Anthony", "Deni Avdija"]

=> ["LaMelo Ball", "Onyeka Okongwu", "Anthony Edwards", "Killian Hayes", "James Wiseman", "Obi Toppin", "Tyrese Haliburton", "Aleksej Pokuseveski", "Cole Anthony", "Deni Avdija"]
```
Note that the implicit return is the entire array.

Now let's build our `each` method line by line.

```
irb(main):002:0> nba_draft_top_10.each do |prospect|
irb(main):003:1* puts "#{prospect} is going to be good value where he is picked."
irb(main):004:1> end
LaMelo Ball is going to be good value where he is picked.
Onyeka Okongwu is going to be good value where he is picked.
Anthony Edwards is going to be good value where he is picked.
Killian Hayes is going to be good value where he is picked.
James Wiseman is going to be good value where he is picked.
Obi Toppin is going to be good value where he is picked.
Tyrese Haliburton is going to be good value where he is picked.
Aleksej Pokuseveski is going to be good value where he is picked.
Cole Anthony is going to be good value where he is picked.
Deni Avdija is going to be good value where he is picked.

=> ["LaMelo Ball", "Onyeka Okongwu", "Anthony Edwards", "Killian Hayes", "James Wiseman", "Obi Toppin", "Tyrese Haliburton", "Aleksej Pokuseveski", "Cole Anthony", "Deni Avdija"]
```
Now notice how the iterator runs automatically after pressing 'enter'. But just as important, notice after the iterator finishes, it implicitly returns the *original* array. This is the purpose of `each`, to perform an action with a collection of data without mutating the original source.

There are actually several variations of `each` iterators like `each_with_index`, `each_char`, `each_with_object` and more. I will touch on `each_with_index`, but other `each` methods you should consult the [Ruby Docs](https://www.ruby-doc.org).

`each_with_index` will do the same thing as `each`, but takes a second argument inside the `||` block that will give a visible numerical value to the index. So if we wanted to turn our big board into rankings instead of a general top 10, we could do this:

```ruby
nba_draft_top_10 = ["LaMelo Ball", "Onyeka Okongwu", "Anthony Edwards", "Killian Hayes", "James Wiseman", "Obi Toppin", "Tyrese Haliburton", "Aleksej Pokuseveski", "Cole Anthony", "Deni Avdija"]


nba_draft_top_10.each_with_index do |prospect, index|
    puts "I have #{prospect} rated ##{index+1} overall."
end

#=>I have LaMelo Ball rated #1 overall.
#I have Onyeka Okongwu rated #2 overall.
#I have Anthony Edwards rated #3 overall.
#I have Killian Hayes rated #4 overall.
#I have James Wiseman rated #5 overall.
#I have Obi Toppin rated #6 overall.
#I have Tyrese Haliburton rated #7 overall.
#I have Aleksej Pokuseveski rated #8 overall.
#I have Cole Anthony rated #9 overall.
#I have Deni Avdija rated #10 overall.
``` 
So in essence, the second argument in the block effectively substitutes for a counter variable & incrementer. 

Also, James Wiseman is probably lower on my big board. I value Onyeka Okongwu's defensive versatility over Wiseman's more likely gaudy stats and think most others here will have a more well-rounded game.

<h1>Map & it's Perhaps Evil Twin Collect</h1>
So I know I mentioned there were 2 primary iterators, then snuck in that `each` has an extended family, but now I'm also adding a potentially evil twin? Yup, but I am exaggerating on the evil part. `Map` and `collect` are different names for basically doing the same thing: iterating through a collection and *mutating* that collection. I singled out `collect` as the evil twin for 2 reasons:

**1.** `Map`is a more universal term for this action and is used in many programming languages.
**2.** `Collect` has more letters in it.

So let's open up a new irb session and use the same function we used with `each`:

```
irb(main):001:0> nba_draft_top_10 = ["LaMelo Ball", "Onyeka Okongwu", "Anthony Edwards", "Killian Hayes", "James Wiseman", "Obi Toppin", "Tyrese Haliburton", "Aleksej Pokuseveski", "Cole Anthony", "Deni Avdija"]

=> ["LaMelo Ball", "Onyeka Okongwu", "Anthony Edwards", "Killian Hayes", "James Wiseman", "Obi Toppin", "Tyrese Haliburton", "Aleksej Pokuseveski", "Cole Anthony", "Deni Avdija"]
irb(main):002:0> nba_draft_top_10.map do |prospect|
irb(main):003:1* puts "#{prospect} is going to be good value where he is picked."
irb(main):004:1> end
LaMelo Ball is going to be good value where he is picked.
Onyeka Okongwu is going to be good value where he is picked.
Anthony Edwards is going to be good value where he is picked.
Killian Hayes is going to be good value where he is picked.
James Wiseman is going to be good value where he is picked.
Obi Toppin is going to be good value where he is picked.
Tyrese Haliburton is going to be good value where he is picked.
Aleksej Pokuseveski is going to be good value where he is picked.
Cole Anthony is going to be good value where he is picked.
Deni Avdija is going to be good value where he is picked.

=> [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil]
```

Check out the implicit return. See how we are now returning `nil` where we used to return prospect names? Well, it returns `nil` due to a combination of `map` mutating the array and `puts` is inherently `nil`. If we want to see the changes reflected, we just have to remove the `puts`.

```
irb(main):005:0> nba_draft_top_10.map do |prospect|
irb(main):006:1* "#{prospect} is going to be good value where he is picked."
irb(main):007:1> end
=> ["LaMelo Ball is going to be good value where he is picked.", "Onyeka Okongwu is going to be good value where he is picked.", "Anthony Edwards is going to be good value where he is picked.", "Killian Hayes is going to be good value where he is picked.", "James Wiseman is going to be good value where he is picked.", "Obi Toppin is going to be good value where he is picked.", "Tyrese Haliburton is going to be good value where he is picked.", "Aleksej Pokuseveski is going to be good value where he is picked.", "Cole Anthony is going to be good value where he is picked.", "Deni Avdija is going to be good value where he is picked."]
```

Now we see something really interesting. We no longer have the `puts` command to display our function item by item like in previous examples. Instead, we definitively see the mutations we made to the array reflected in a *new array* produced by the `map` iterator that contains all the returned values. 

For good measure, here is that same iteration using `collect`:

```
irb(main):008:0> nba_draft_top_10.collect do |prospect|
irb(main):009:1* "#{prospect} is going to be good value where he is picked."
irb(main):010:1> end
=> ["LaMelo Ball is going to be good value where he is picked.", "Onyeka Okongwu is going to be good value where he is picked.", "Anthony Edwards is going to be good value where he is picked.", "Killian Hayes is going to be good value where he is picked.", "James Wiseman is going to be good value where he is picked.", "Obi Toppin is going to be good value where he is picked.", "Tyrese Haliburton is going to be good value where he is picked.", "Aleksej Pokuseveski is going to be good value where he is picked.", "Cole Anthony is going to be good value where he is picked.", "Deni Avdija is going to be good value where he is picked."]
```

Zero discernable differences.

<h1>Mutable vs Immutable</h1>
Since mutation is the key difference with these iterators, it's probably best if we touch on what situations to use each. I'm hardly the best to explain, I did find a really good article [here](https://blog.logrocket.com/to-mutate-or-immutate/), though it covers JavaScript uses the principle will remain the same. Now for most of what you will do while you are starting out, functionally, there will be little difference between mutable and immutable iterations. However, when you start building more complex apps, this nuance becomes more important. This is an over-generalization to be sure, but here it is:

**1.** Use `each` when you are iterating through your back-end data. This is the stuff that you wouldn't want to permanently manipulate as it could affect other parts of your app. When using Ruby on Rails, your Controllers are a good example of places you want to use `each`.
**2.** Use `map` to iterate and manipulate data you want the user to see (front-end). `Map` is often less bulky code-wise to work with since it naturally preserves itself by returning a new array. 

<h1>Ruby Iterations Lab</h1>
Let's go `collect` some practice to help `map` out `each` of our skills in the [Ruby Iterations Lab](https://github.com/BMWeygant/Ruby-Array_Iteration-Lab)!