Episode #632
Loops!

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.

Episode Script

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 step_goal variable.

To start, let’s introduce a new command: putc

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

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 step_goal.

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 end keyword.

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 loop_count.

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 += operator.

  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,

we use 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 \n .

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 step_goal.

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 char_count equals step_goal.

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:

  1. Initialize a loop counter;
  2. Do some work
  3. Increment the loop counter
  4. 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 for loop.

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 for.

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.

The 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

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 step_goal.

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.

Happy hacking!