In the past 2 lessons, we learned about the more complex data types: Arrays & Hashes.  In software development, we are often dealing with collections (or lists) of complex data objects (hashes).  So what can we do with lists of data?

Here's a list of friends:

friends = ['Rachel', 'Monica', 'Phoebe', 'Ross', 'Chandler', 'Joey']

If we want to say hi to each friend, here's the code that we would write, given what we know:

greeting = "Hi #{friends[0]}"
puts greeting # Hi Rachel

greeting = "Hi #{friends[1]}"
puts greeting # Hi Monica

greeting = "Hi #{friends[2]}"
puts greeting # Hi Phoebe

greeting = "Hi #{friends[3]}"
puts greeting # Hi Ross

greeting = "Hi #{friends[4]}"
puts greeting # Hi Chandler

greeting = "Hi #{friends[5]}"
puts greeting # Hi Joey

There's nothing technically incorrect about this code, but it has 2 weaknesses.

First, it's repetitive.  There's a principle in software development known as DRY - Don't Repeat Yourself.  Repetitive code is prone to error - more opportunity for typos and when you need to make a change, you need to make that change everywhere that code exists.  For example, maybe we want to say "Hey" instead of "Hi".  Lots of code needs to change:

greeting = "Hey #{friends[0]}"
puts greeting # Hey Rachel

greeting = "Hey #{friends[1]}"
puts greeting # Hey Monica

greeting = "Hey #{friends[2]}"
puts greeting # Hey Phoebe

greeting = "Hey #{friends[3]}"
puts greeting # Hey Ross

greeting = "Hey #{friends[4]}"
puts greeting # Hey Chandler

greeting = "Hey #{friends[5]}"
puts greeting # Hey Joey

Second, it doesn't easily adapt to changes in the underlying data.  What if we add "Marcel the Monkey" to our friend list?  We'd need to modify our code:

greeting = "Hey #{friends[0]}"
puts greeting # Hey Rachel

greeting = "Hey #{friends[1]}"
puts greeting # Hey Monica

greeting = "Hey #{friends[2]}"
puts greeting # Hey Phoebe

greeting = "Hey #{friends[3]}"
puts greeting # Hey Ross

greeting = "Hey #{friends[4]}"
puts greeting # Hey Chandler

greeting = "Hey #{friends[5]}"
puts greeting # Hey Joey

greeting = "Hey #{friends[6]}"
puts greeting # Hey Marcel the Monkey

If we end up adding dozens or hundreds of friends to our array, our code would get very very long!

Or if we remove Marcel from the list, our code will be buggy since friends[6] will be nil so the output would just be "Hey " with an empty space.

Data frequently changes; new rows get added to and removed from the database all the time.  Ideally our code wouldn't care how many elements are in the list.  So we need some way to automate these repetitive steps.  We need a loop.

Loops

A loop is a programming construct that repeats an operation some number of times.  Before we write the code for a loop, let's conceptualize the steps.

# 1. Set index = 0
# 2. Read from the array at the index
# 3. Construct a sentence that includes "Hi" and the value from step 2
# 4. Display the sentence
# 5. If this is the last element in the array, go to step 8
# 6. Increment the index by adding 1
# 7. Repeat (i.e. go back to step 2)
# 8. End

The key steps that make this a loop are steps 5 and 7, which together instruct our little program to keep doing the same thing over and over until there's nothing left in the array and then stop.  With this approach, it won't matter if the array has 6 friends, or 2 friends, or 100 friends - the code does not care.  As a side note, this conceptualization is often referred to as pseudo-code.  We might even include it as a code comment so that we can easily follow the steps when writing the actual code.

Let's convert the pseudo-code into ruby code:

# 1. Set index = 0
index = 0

# 2. Read from the array at the index
friend = friends[index]

# 3. Construct a sentence that includes "Hi" and the value from step 2
greeting = "Hi #{friend}"

# 4. Display the sentence
puts greeting

# 5. If this is the last element in the array, go to step 8
# ???

# 6. Increment the index by adding 1
index = index + 1

# 7. Repeat (i.e. go back to step 2)
# ???

# 8. End
# ???

We're making progress, but now we need some loop code to complete it.  There are several ways to write a loop.  Here's 1 way.

loop do
  puts "looping"
end

The above code will repeat forever (displaying the text "looping").  When the code gets to the end, it will iterate, meaning it will go back to the beginning of the loop and start again.  To make it stop, we need to "break" out of the loop on some condition.  A common condition is to break out when a counter reaches some value:

counter = 0
loop do
  if counter == 5
    break
  end
  puts "looping 5 times"
  counter = counter + 1
end

The loop will now check on each iteration if the counter has reached 5 and once it has (meaning it ran 5 times as value 0, 1, 2, 3, & 4), then the break line of code moves to the end and the loop is over.  If it isn't yet 5, the code will display the text "looping 5 times" and then increment the value of the counter.  First the counter is 0, then 1, then 2, then 3, then 4, and finally 5 before the loop ends.

Now that we know how to write a loop, let's return to our friends array.

Let's wrap lines 2-8 with a ruby loop:

# 1. Set index = 0
index = 0

# begin the loop
loop do

  # 2. Read from the array at the index
  friend = friends[index]

  # 3. Construct a sentence that includes "Hi" and the value from step 2
  greeting = "Hi #{friend}"

  # 4. Display the sentence
  puts greeting

  # 5. If this is the last element in the array, go to step 8
  # - if there are 6 elements in the array, the last is at index 5
  if index == friends.length - 1
    break
  end

  # 6. Increment the index by adding 1
  index = index + 1

  # 7. Repeat (i.e. go back to step 2)
  # code goes back to the beginning of the loop

# 8. End
end

Step 5 now includes the functionality to break out of the loop on some condition, in this case when the index is at the last position of the array.  Recall that since arrays start at index 0, the last index will be the length of the array - 1 (e.g. if there are 2 elements in the array, the indices are 0 & 1).  This is fine, except that in the situation where there is nothing in the array, we shouldn't perform steps 2-4.  So let's move the break logic up to the beginning.

# 1. Set index = 0
index = 0

# begin the loop
loop do

  # 2. If the index has reached the end of the array, go to step 8
  # - if there are 0 elements in the array, break when the index is 0
  # - if there are 2 elements in the array, break when the index is 2
  if index == friends.length
    break
  end

  # 3. Read from the array at the index
  friend = friends[index]

  # 4. Construct a sentence that includes "Hi" and the value from step 2
  greeting = "Hi #{friend}"

  # 5. Display the sentence
  puts greeting

  # 6. Increment the index by adding 1
  index = index + 1

  # 7. Repeat (i.e. go back to step 2)
  # code goes back to the beginning of the loop

# 8. End
end

If it helps to visualize it without the comments, here is the final code:

index = 0
loop do
  if index == friends.length
    break
  end

  friend = friends[index]
  greeting = "Hi #{friend}"
  puts greeting

  index = index + 1
end

Simplifying

In many programming languages, we need to keep track of the index, just as we've done in our loop code above.  But ruby is all about developer happiness. Instead of keeping track of the position in the array, and retrieving each element by position, we can have ruby do this work for us and simply hand us each element in the array, one at a time, for us to work with.  So our steps get a little simpler:

# 1. Read from the array
# 2. If we've already read all of the elements from the array,
#    go to step 6
# 3. Construct a sentence that includes "Hi" and the value from step 1
# 4. Display the sentence
# 5. Repeat (i.e. go back to step 1)
# 6. End

This loop uses the following structure:

for __________ in __________
  # code to repeat
end

Steps 1, 2 and 5 are all built into this for code.  The first blank is a variable that you choose to represent a single element in the array.  It's sort of like writing friend = friends[index] from step 3 in the previous loop code.  Essentially, it assigns friend = friends[0] on the first iteration.  And then friend = friends[1] on the next iteration of the loop.  And so on.  The second blank is the array itself, in our case friends.  Here's our code:

# 1. Read from the array
for friend in friends

  # 2. If we've already read all of the elements from the array,
  #    go to step 6
  # Already part of for ____ in ____

  # 3. Construct a sentence that includes "Hi" and the value from step 1
  greeting = "Hi #{friend}"

  # 4. Display the sentence
  puts greeting

  # 5. Repeat (i.e. go back to step 1)
  # Already part of for ____ in ____

# 6. End
end

And just to clean it up without the pseudo-code:

for friend in friends
  greeting = "Hi #{friend}"
  puts greeting
end

Notice how the code inside the for block of code is pretty much the same code that we started with.

greeting = "Hi #{friends[0]}"
puts greeting # Hi Rachel

The only thing we changed is we don't need to explicitly read from the array at each index.  This for loop hides all of the gory details for us. We don't have to keep track of the position or repeat ourselves. Instead, ruby will do all of that on our behalf, and automatically fill in the friend variable with the actual value of each element, one at a time.

As with almost everything in ruby, there are other ways to loop through arrays, but this is the code we'll be using to keep things simple.

Lab

Time for a lab to practice looping through arrays. Instructions are in the file 5-loops.rb in the labs directory.