In this rare beginner-oriented episode, we’re going to talk about looping constructs in Ruby, starting from the most basic infinite loops, and building up to for/in loops.
Video transcript & code
Today’s episode is going to be a lot more beginner-oriented than most. I’m making this episode because I have a friend who is learning programming, and I realized I have some opinions about how to teach the concept of loops in programming languages. If you already know everything in this episode I apologize, but I hope it will be useful to you in teaching someone else how to code!
Let’s say we’ve been challenged to print some stair-steps on the screen like this:
# ## ### #### #####
In Ruby, we can print a line of text to the screen using the
puts command and a quoted string.
puts "Hello, world"
When we run this script from the command line, we see the text printed out.
$ ruby hello.rb Hello, world
For this video though, I’m going to use a little bit of magic to run my scripts right in the editor, like this.
puts "Hello, world" # >> Hello, world
When I do this, you can see the output at the bottom of the script, after a hash mark and a pair of greater-than symbols, like this.
# >> Hello, world
I’m using this trick because it makes it a little faster to demonstrate code. Just remember that on your machine, you can always run the script from a command-line.
So since we know how to output a line text, that means we could print our stair-steps using a series of
puts commands, like this.
puts " #" puts " ##" puts " ###" puts " ####" puts "#####" # >> # # >> ## # >> ### # >> #### # >> #####
Notice that in order to make the steps ramp up to the right, we have to pad the higher steps with space characters. These space characters effectively “push” the hash characters to the right.
OK, that works. But now let’s add a new challenge:
We want to be able to set a number of steps in a variable at the top of the program.
step_goal = 7
And then the program should print exactly that number of steps.
The program we have right now doesn’t do this. The number of steps is currently what we call “hard-coded”: it can only produce exactly one kind of output. We need to change this code to be more dynamic: it needs to change its output based on the input. In this case, the input is the
To start, let’s introduce a new command:
putc "H" # >> H
This command outputs exactly one character. And unlike
puts, it doesn’t automatically go to the next line after outputting that character.
We can see this if we output some more characters with
putc "H" putc "e" putc "l" putc "l" putc "o" # >> Hello
See how they all came out on one line?
putc is the command we’re going to use to build a single line of our stair-step pattern.
The first line of the stair-step pattern is the tippy top of the steps.
So if the
step_goal is set to 7, then we need to output 6 spaces followed by a hash.
step_goal = 7 putc " " putc " " putc " " putc " " putc " " putc " " putc "#" # >> #
…except that we don’t want to hard-code this anymore. We want it to automatically write the correct number of spaces and hash marks based on the given
And that’s where a loop can help.
In Ruby, we can start a loop with the
loop command, followed by the
do keyword, and then close it with an
Inside the loop block, we can write some code that we want to happen repeatedly. This code will write some text to the screen, and then pause for one second.
loop do puts "Hello from a loop!" sleep 1 end
When we run this script from a command line, we can see what
loop does: it just keeps running the same code over and over again!
$ ruby 06_loop.rb Hello from a loop! Hello from a loop! Hello from a loop! Hello from a loop! Hello from a loop! Hello from a loop!
It will do this forever, unless we interrupt the program by pressing Ctrl-C.
Hello from a loop! ^CTraceback (most recent call last): 3: from 06_loop.rb:1:in `<main>' 2: from 06_loop.rb:1:in `loop' 1: from 06_loop.rb:3:in `block in <main>' 06_loop.rb:3:in `sleep': Interrupt
So now you have an idea of what a
loop is: it’s a construct that causes some code to be run, then “loops around” to the beginning of the code, and runs it again. And then again, and again, and again… forever.
Repeating a process over and over is often called iteration, in programming terms. And most of the time, we don’t actually want to iterate forever. We want to iterate a fixed number of times, and then stop and move on to the next task.
We can make our loop iterate a certain number of times and then stop with a loop counter variable. We’ll call it
Inside the loop, we add an
if statement to check if the loop counter has reached a limit. Let’s make that limit 5, for now.
If it has reached 5, we use the
break command. This tells Ruby to break out of the loop. In other words, to stop iterating over the same code, and move on.
Now we need to do one more thing to make this work.
We add some code to add one to the loop counter variable every time the loop repeats.
Finally, let’s add a
puts after the loop, to show that we got out.
When we run this script, we can see that the loop repeated five times… and then it stopped repeating, and moved on to the next thing.
loop_count = 0 loop do if loop_count == 5 break end puts "Hello from a loop!" sleep 1 loop_count = loop_count + 1 end puts "Whew, I got out of that loop!" # >> Hello from a loop! # >> Hello from a loop! # >> Hello from a loop! # >> Hello from a loop! # >> Hello from a loop! # >> Whew, I got out of that loop!
Before we move on, let’s make one more tweak to this code.
We can shrink the line that increments the
loop_count by one down a bit using the
loop_count += 1
This operator adds the given number to a variable’s value. In Ruby as in many programming languages, it’s idiomatic to use this operator when incrementing a loop variable.
Now we know enough about looping to produce the first line of our stair-steps!
We know that for the top of a 7-layer set of stairsteps, we need to print six spaces followed by a hash.
So let’s set a “spaces needed” variable to the number of steps minus one.
Then let’s make a loop counter. We’ll call it
char_count, because it’s the count of characters we’ve outputted so far.
Now we’ll start our loop.
If the current count of characters we’ve output is less than the number of spaces we need,
putc to output a space.
Otherwise, we must be done printing spaces. If the number of characters we’ve printed is still less than the goal,
We need to fill in the rest of the line with hash marks.
(By the way,
elsif is a Ruby shortcut for saying “if the last condition wasn’t true, see if this condition is true instead!”)
If neither of those conditions is true, Then we must have reached our goal number of characters,
so it’s time to break out of the loop.
Let’s not forget to update the number of characters we’ve output!
Finally, we need to end the current line with a “newline” character, which is represented by the special code
When we run this it’s a bit anticlimactic.
But when we carefully count up the spaces that were printed out, we can see that our loop correctly printed the top line of our stair-step pattern!
Let’s play with this a little bit.
We’ll change the step count to 5 and the space count to 3, then run it again.
step_goal = 5 space_goal = step_goal - 3 char_count = 0 loop do if char_count < space_goal putc " " elsif char_count < step_goal putc "#" else break end char_count += 1 end putc "\n" # >> ###
Focusing in on the output, we can see that just changing a couple of variables caused the output to be changed!
We still need to make our program generate a complete multi-line stair-step pattern. But first, let’s talk about loop constructs a bit more.
Up til now, we’ve been using Ruby’s most basic looping construct, the
loop command. But most of the time when we’re programming with loops, we don’t use such low-level constructs.
It’s extremely common in loops to have a “stop condition” that is checked at every iteration. So common, in fact, that most programming languages have a form of loop that automatically checks a condition every time it loops around.
It’s called a
while loop. And we can use one to keep looping only while the
char_count is less than the
Then we can get rid of the
if branch that handles breaking out of the loop. We no longer need it, because our
while condition will ensure that the loop breaks once
While we’re at it, we can convert the
elsif branch that handles outputting hash marks into a simple
else. We can only get to this branch if the character count is greater than or equal to the space count. And since the
while loop will finish before we can output too many hash marks, we don’t have to worry about an upper bound here.
step_count = 5 space_count = step_count - 3 char_count = 0 while char_count < step_count if char_count < space_count putc " " else putc "#" end char_count += 1 end putc "\n" # >> ###
Checking a condition at each iteration isn’t the only element that’s common to most loops. In fact, there’s a four-part pattern most loops fall into:
- Initialize a loop counter;
- Do some work
- Increment the loop counter
- Check the loop condition and break out of the loop if the counter has reached a goal value
1. initialize a loop counter 2. do some work 3. increment the loop counter 4. break if the loop counter has reached a goal value
This four-part pattern is so common, that many programming languages have a special loop form that formalizes it! It’s called a
In a lot of programming languages, a
for loop looks something like this:
step_goal = 5 space_goal = step_goal - 3 for(char_count = 0; char_count < step_goal; char_count += 1) if char_count < space_goal putc " " else putc "#" end end putc "\n" # >> ###
there’s the keyword
Then the iteration variable is introduced and initialized
then there’s a semicolon, followed by the condition which should be checked at each iteration of the loop;
Then another semicolon and some code to increment the loop counter. Note that even though it’s placed at the top of the
for loop, this increment code is normally run after the body of the loop.
Now that the loop variable increment code is here, we can remove it from the body of the loop.
for loop encapsulates and formalizes the four parts of a typical loop: initializing a loop counter, checking the counter, incrementing the counter, and doing some work. It doesn’t do anything more than the basic loops we wrote earlier. It just lets us write typical loops more concisely.
That said, however, the Ruby programming language doesn’t actually have this kind of for-loop! If we tried to run this code, it wouldn’t work.
Instead, Ruby has an even more abstract and simplified form of
for loop, known as a “for/in” loop.
Here’s how we write it: for
char_count in zero through
step_goal = 5 space_goal = step_goal - 3 for char_count in 0..step_goal if char_count < space_goal putc " " else putc "#" end end putc "\n" # >> ###
That’s it! That line is enough to give Ruby three of the four parts of a loop. It says to initialize a loop variable called
char_count to zero. Then it should keep repeating the code inside the loop, each time setting
char_count to the next number between 0 and
step_goal. Once it gets to
step_goal it should stop looping and move on.
OK! Now that we’ve streamlined this loop a bit, let’s finish our program.
We’ve been dynamically outputting characters in a line, using a loop. How can we dynamically output the right number of lines to make stair-steps? We can use a loop for that too!
We’ll add a loop outside our line-printing loop. In this loop we’ll call the loop counter variable
line_count, and we’ll step it from one through the
We do need to make a finicky modification to this loop. If the step goal is 5 and we start at zero and print lines all the way up to five, we’d actually wind up printing six lines. To stop at five,
We change the loop variable range to exclude its ending number, which in Ruby means switching from two to three dots.
Each time through the loop, it will set a spaces needed equal to the number of total steps minus the step number we’re currently on
After that, it just runs our old familiar line-printing loop.
step_goal = 5 for line_count in 1..step_goal space_goal = step_goal - line_count for char_count in 0..step_goal if char_count < space_goal putc " " else putc "#" end end putc "\n" end # >> # # >> ## # >> ### # >> #### # >> #####
When we run this code, we get our stair-steps!
And if we change the
step_goal and run it again… we get a differently-sized set of stairs!
step_goal = 10 for line_count in 1..step_goal space_goal = step_goal - line_count for char_count in 0..step_goal if char_count < space_goal putc " " else putc "#" end end putc "\n" end # >> # # >> ## # >> ### # >> #### # >> ##### # >> ###### # >> ####### # >> ######## # >> ######### # >> ##########
This is what we call a “nested loop”, and it’s a pretty common occurrence in programming!
So today we’ve learned about loops. We’ve learned that loops are a way of doing things over and over again, otherwise known as iterating. We’ve seen how most loops break down into four parts: setting up a loop counter variable, checking the variable’s value, doing some work, and then updating the loop counter variable. And we’ve seen some common loop variations that conveniently formalize those four steps. Culminating in the for/in loop, which lets us take care of three of those steps in one easy-to-read line.