06. Flow Control

Ahhhh, flow control. This is where it all comes together. Although this chapter is shorter and easier than the methods chapter, it will open up a world of programming possibilities. After this chapter, we'll be able to write truly interactive programs; in the past we've written programs that say different things depending on what you type, but after this chapter they will do different things, too. But before we can do that, we need to be able to compare objects in our program. We need...

Comparison Methods

Let's get this over with quickly so we can move on to branching, which is where the cool stuff happens. To see if one object is greater than or less than another, we use the > and < methods, like this:

puts 1 > 2
puts 1 < 2
false
true

No problem. Likewise, we can find out if an object is greater-than-or-equal-to (or less-than-or-equal-to) another with the >= and <= methods.

puts 5 >= 5
puts 5 <= 4
true
false

Finally, we can find out if two objects are equal or not using == (which means "are these objects equal?") and != (which means "are these objects different?"). It's important not to confuse = with ==. = is for telling a variable to point at an object (assignment), and == is for asking the question: "Are these two objects equal?".

puts 1 == 1
puts 2 != 1
true
true

And of course we can compare strings, too. When strings are compared, they compare their lexicographical ordering, which basically means their dictionary ordering. cat comes before dog in the dictionary, so:

puts 'cat' < 'dog'
true

There is a catch, though: computers usually think of capital letters as coming before lowercase letters (that's how they store them in fonts, for example: all the capital letters first, then the lowercase ones). This means that the computer will think 'Zoo' comes before 'ant', so if you want to figure out which word would come first in a real dictionary, make sure to use downcase (or upcase or capitalize) on both words before you try to compare them.

One last note before Branching: the comparison methods aren't giving us the strings 'true' and 'false'; they are giving us the special objects true and false (of course, true.to_s gives us 'true', which is why puts printed 'true'). true and false are used all the time in...

Branching

"Branching" is a simple but powerful concept. In fact, it's so simple that I bet I don't even have to explain it at all; I'll just show it to you:

puts 'Hello, what\'s your name?'
name = gets.chomp
puts 'Hello, ' + name + '.'

if name == 'Chris'
  puts 'What a lovely name!'
end
Hello, what's your name?
Chris
Hello, Chris.
What a lovely name!

But if we put in a different name...

Hello, what's your name?
Chewbacca
Hello, Chewbacca.

And that is branching. If what comes after the if is true, we execute the code between the if and the end. If what comes after the if is false, we don't. Plain and simple.

I indented the code between the if and the end because I think it makes it easier to keep track of the branching. Almost all programmers do this, regardless of what language they are coding in. It may not seem that helpful in this simple case, but when things get more complex, it makes a huge difference.

Often we would like a program to do one thing if an expression is true, and another if it is false. That's what else is for:

puts 'I am a fortune-teller.  Tell me your name:'
name = gets.chomp

if name == 'Chris'
  puts 'I see great things in your future.'
else
  puts 'Your future is... oh my!  Look at the time!'
  puts 'I really have to go, sorry!'
end
I am a fortune-teller.  Tell me your name:
Chris
I see great things in your future.

Now let's try a different name...

I am a fortune-teller.  Tell me your name:
Ringo
Your future is... oh my!  Look at the time!
I really have to go, sorry!

Branching is kind of like coming to a fork in the code: do we take the path for people whose name == 'Chris', or else do we take the other path?

And just like the branches of a tree, you can have branches that contain branches of their own:

puts 'Hello, and welcome to seventh grade English.'
puts 'My name is Mrs. Gabbard.  And your name is...?'
name = gets.chomp

if name == name.capitalize
  puts 'Please take a seat, ' + name + '.'
else
  puts name + '?  You mean ' + name.capitalize + ', right?'
  puts 'Don\'t you even know how to spell your name??'
  reply = gets.chomp

  if reply.downcase == 'yes'
    puts 'Hmmph!  Well, sit down!'
  else
    puts 'GET OUT!!'
  end
end
Hello, and welcome to seventh grade English.
My name is Mrs. Gabbard.  And your name is...?
chris
chris?  You mean Chris, right?
Don't you even know how to spell your name??
yes
Hmmph!  Well, sit down!

Fine, I'll capitalize it...

Hello, and welcome to seventh grade English.
My name is Mrs. Gabbard.  And your name is...?
Chris
Please take a seat, Chris.

Sometimes it might get confusing trying to figure out where all the ifs, elses, and ends go. What I do is write the end at the same time I write the if. So as I was writing the above program, it first looked like this:

puts 'Hello, and welcome to seventh grade English.'
puts 'My name is Mrs. Gabbard.  And your name is...?'
name = gets.chomp

if name == name.capitalize
else
end

Then I filled it in with comments, stuff in the code the computer will ignore:

puts 'Hello, and welcome to seventh grade English.'
puts 'My name is Mrs. Gabbard.  And your name is...?'
name = gets.chomp

if name == name.capitalize
  #  She's civil.
else
  #  She gets mad.
end

Anything after a # is considered a comment (unless, of course, you are in a string). After filling in the comments, I replaced them with working code. Some people like to leave the comments in; personally, I think well-written code speaks for itself. I used to write more comments, but as I get more "fluent" in Ruby, I write them less and less. I actually find them distracting much of the time. It's a personal choice; you'll find your own style (usually your style will evolve). So my next step looked like this:

puts 'Hello, and welcome to seventh grade English.'
puts 'My name is Mrs. Gabbard.  And your name is...?'
name = gets.chomp

if name == name.capitalize
  puts 'Please take a seat, ' + name + '.'
else
  puts name + '?  You mean ' + name.capitalize + ', right?'
  puts 'Don\'t you even know how to spell your name??'
  reply = gets.chomp

  if reply.downcase == 'yes'
  else
  end
end

Again, I wrote the if, else, and end all at the same time. It really helps me keep track of "where I am" in the code. It also makes the job seem easier because I can focus on one small part, like filling in the code between the if and the else. The other benefit of doing it this way is that the computer can understand the program at any stage. Every one of the unfinished versions of the program I showed you would run. They weren't finished, but they were working programs. That way I could test it as I wrote it, which helped see how it was coming along and where it still needed work. When it passed all the tests, I knew I was done!

These tips will help you with writing programs that branch, but they also help with the other main type of flow control:

Looping

Often, you'll want your computer to do the same thing over and over again—after all, that's what computers are supposed to be good at.

When you tell your computer to keep repeating something, you also need to tell it when to stop. Computers never get bored, so if you don't tell it to stop, it won't. We make sure this doesn't happen by telling the computer to repeat certain parts of a program while a certain condition is true. This works very similarly to how if works:

command = ''

while command != 'bye'
  puts command
  command = gets.chomp
end

puts 'Come again soon!'
Hello?
Hello?
Hi!
Hi!
Very nice to meet you.
Very nice to meet you.
Oh... how lovely!
Oh... how lovely!
bye
Come again soon!

And that's a loop. (You may have noticed the blank line at the beginning of the output; it comes from the first puts, before the first gets. How would you change the program to get rid of this first line? Test it! Did it work exactly like the program above, other than that first blank line?)

Loops allow you to do all sorts of interesting things, as I'm sure you can imagine. However, they can also cause problems if you make a mistake. What if your computer gets stuck in an infinite loop? If you think this may have happened, just hold down the Ctrl key and press C.

Before we start playing around with loops, though, let's learn a few things to make our job easier.

A Little Bit of Logic

Let's look at our first branching program again. What if my wife came home, saw the program, tried it, and it didn't tell her what a lovely name she had? I wouldn't want to hurt her feelings (or sleep on the couch), so let's rewrite it:

puts 'Hello, what\'s your name?'
name = gets.chomp
puts 'Hello, ' + name + '.'

if name == 'Chris'
  puts 'What a lovely name!'
else
  if name == 'Katy'
    puts 'What a lovely name!'
  end
end
Hello, what's your name?
Katy
Hello, Katy.
What a lovely name!

Well, it works... but it isn't a very pretty program. Why not? The best rule I ever learned about programming was the DRY rule: Don't Repeat Yourself. I could write a small book on why that is such a good rule. In our case, we repeated the line What a lovely name!. Why is this a problem? Well, what if I made a typo when I rewrote it? What if I wanted to change lovely to beautiful in both lines? I'm lazy, remember? Basically, if I want my program to do the same thing when it gets Chris or Katy, then it should really do the same thing:

puts 'Hello, what\'s your name?'
name = gets.chomp
puts 'Hello, ' + name + '.'

if (name == 'Chris' or name == 'Katy')
  puts 'What a lovely name!'
end
Hello, what's your name?
Katy
Hello, Katy.
What a lovely name!

Much better. In order to make it work, I used or. The other logical operators are and and not. It is always good to use parentheses when working with these. Let's see how they work:

iAmChris  = true
iAmPurple = false
iLikeFood = true
iEatRocks = false

puts (iAmChris and iLikeFood)
puts (iLikeFood and iEatRocks)
puts (iAmPurple and iLikeFood)
puts (iAmPurple and iEatRocks)
puts
puts (iAmChris or iLikeFood)
puts (iLikeFood or iEatRocks)
puts (iAmPurple or iLikeFood)
puts (iAmPurple or iEatRocks)
puts
puts (not iAmPurple)
puts (not iAmChris)
true
false
false
false

true
true
true
false

true
false

The only one that might trick you is or. In English, we often use "or" to mean "one or the other, but not both." For example, your mom might say, "For dessert, you can have pie or cake." She did not mean you could have both! A computer, on the other hand, uses or to mean "one or the other, or both." (Another way to say it is, "at least one of these is true.") This is why computers are more fun than moms.

A Few Things to Try

  • "99 Bottles of Beer on the Wall..." Write a program which prints out the lyrics to that beloved classic, that goes up to 100 bottles.
  • Write a Deaf Grandma program. Whatever you say to grandma (whatever you type in), she should respond with HUH?! SPEAK UP, SONNY!, unless you shout it (type in all capitals). If you shout, she can hear you (or at least she thinks so) and yells back, NO, NOT SINCE 1938! To make your program really believable, have grandma shout a different year each time; maybe any year at random between 1930 and 1950. (This part is optional, and would be much easier if you read the section on Ruby's random number generator at the end of the methods chapter.) You can't stop talking to grandma until you shout BYE.
    • Hint: Don't forget about chomp! 'BYE' with an Enter is not the same as 'BYE' without one!
    • Hint 2: Try to think about what parts of your program should happen over and over again. All of those should be in your while loop.
  • Extend your Deaf Grandma program: What if grandma doesn't want you to leave? When you shout BYE, she could pretend not to hear you. Change your previous program so that you have to shout BYE three times in a row. Make sure to test your program: if you shout BYE three times, but not in a row, you should still be talking to grandma.
  • Leap Years. Write a program which asks for a starting year and an ending year, and then puts all of the leap years between them (and including them, if they are also leap years). Leap years are years divisible by four (like 1984 and 2004). However, years divisible by 100 are not leap years (such as 1800 and 1900) unless they are divisible by 400 (like 1600 and 2000, which were in fact leap years). (Yes, it's all pretty confusing, but not as confusing as having December in the middle of the winter, which is what would happen in the end).

When you finish those, take a break! You've learned a lot already. Congratulations. Are you surprised by the number of things you can tell the computer to do? A few more chapters and you'll be able to program just about anything. Really! Look at all the things you can do now that you couldn't do before learning about loops and branching.

Now let's learn about a new kind of object, one that keeps track of lists of other objects: arrays.