1.9 Loops
1.9.1 if: conditionally doing something once
Consider the following program, which utilizes an if
statement:
Array.new
numbers =
if numbers.length < 10
100)
new_number = rand(
numbers.push(new_number)end
len = numbers.length
p numbers p len
What do you expect the output of this program to be? Try to interpret the program yourself before you ask Ruby to.
Hopefully you clicked “▶ Run”. Did you guess right?
We start off with a blank array, numbers
. If its length is less than 10
(this is true, since length is currently 0
), we push a new random number into it.
Once Ruby reaches the if
’s end
, it proceeds to the next line and continues to execute the rest of the code (whether the if
’s condition was true or not).
At the end of the day, numbers
has one element in it and len
is 1
.
1.9.2 while: conditionally doing something multiple times
Now, consider almost identical code, but with the if
keyword swapped for a new keyword — while
:
Array.new
numbers =
while numbers.length < 10
100)
new_number = rand(
numbers.push(new_number)end
len = numbers.length
p numbers p len
while
works almost exactly like if
— it evaluates the expression next to it, and if the expression is truthy, it executes the code on the lines between it and its end
; if not, it ignores the code on the lines between it and its end
.
There is one key difference: if the condition next to a while
is truthy, after we reach its end
, the execution of the program jumps back up to the while
statement.
Then the condition is evaluated again. If it is still true, then the code inside the while
statement is executed again. And then the execution of the program jumps back up to the while
statement again. Etc.
So in this case,
- The first time we reach the
end
, we jump back up to thewhile
. - Evaluate
a.length < 10
again — still true, since1 < 10
. - So we push in another random number, and jump back up.
2 < 10
? Yep, so we do it again.- We push in another random number, and jump back up.
3 < 10
? Yep, so we do it again.4 < 10
? Yep, so we do it again.- Etc.
10 < 10
? Nope, so now proceed to the line after theend
and continue.- And
len
ends up being10
.
What we’ve seen here is our very first loop; code that is executed multiple times. It could be an arbitrary number of times, perhaps even an infinite number of times if we aren’t careful.
1.9.3 Blocks
Fundamentally, all looping is implemented with while
; but, this being Ruby, there are all sorts of convenience methods on top to make it as easy as possible to create loops for various contexts. For example, let’s say I wanted to print:
"1 Mississippi"
"2 Mississippi"
"3 Mississippi"
# etc
"10 Mississippi"
exactly 10 times. I could do it using while
like this; try interpreting the following code before you click “run”:
1
mississipis =
while mississipis <= 10
" Mississippi"
p mississipis.to_s +
1
mississipis = mississipis + end
Does the code make sense to you?
If the line mississipis = mississipis + 1
looks a little odd to you, you’re not alone. Remember, this is variable assignment, not equivalence. So the expression on the right side (mississipis + 1
) is evaluated first until there’s just one object (e.g 2
) left; and then that object replaces the contents of the variable (mississipis
) named on the left. Rinse and repeat.
Or, rather than while
, I could use Integer
’s .times
method, like this:
1
mississipis =
10.times do
" Mississippi"
p mississipis.to_s +
1
mississipis = mississipis + end
Notice there’s a new keyword here: do
. This is because the .times
method, in order to do its job of executing some code 10 times, needs a special argument — the code to execute.
In order to pass a method some lines of code as an argument, we need to wrap the lines of code within the do
and end
keywords, creating what’s called a block of code.
So, given a block of code, the 10.times
method will execute it for us exactly 10 times; this saves us the trouble of writing a condition for while
.
Block variables
But the .times
method will save us even more trouble than that; we can stop worrying about creating and incrementing the counter variable, mississipis
, too. The .times
method will create a block variable and assign values to it for us automatically, but we have to choose a name for it using some new syntax after the do
: the vertical bars, | |
, or “pipes”. It looks like this:
10.times do |mississipis|
" Mississippi"
p mississipis.to_s + end
Try running it. Here’s what’s going on:
- We created a block of code with
do
/end
and gave it to.times
. - We chose a name for a block variable,
mississipis
, with the| |
after thedo
. - Behind the scenes, the
.times
method didmississipis = 0
before the first iteration. - The
.times
method executed the block of code the first time. - Behind the scenes, the
.times
method didmississipis = 1
before the second iteration. - The
.times
method executed the block of code the second time. - Etc.
Why does .times
start by assigning 0
to its block variable during the first iteration, rather than 1
? Well, that’s just how the author of the .times
method made it work. Remember, Ruby, like many other languages uses zero-indexing.
Fortunately, Ruby provides lots of other looping convenience methods that we can take advantage of instead, and each one assigns different values to its block variable.
In the REPL above, replace 10.times
with each of the following and play around with the arguments to get a sense of how each method works:
5.upto(10)
99.downto(90)
1.step(10, 3)
10.step(1, -4)
Open the GitPod loops project for this chapter and start with the exercise letter_count.rb
:
LTI{Load assignment}(https://github.com/bpurinton-appdev/loops-chapter/tree/bp-additions)[MV4dKHMwdAFhfRn752YW3TAY]{KBpPhe42o6wDRi35rWagKY4F}(20)[loops_project]
For a GitPod refresher, see here.
When you’re done with the first one, work through multiples.rb
And finally, work through fizzbuzz.rb