Flow Control

In many ways, a computer program is like a journey for your data. Along this journey, data encounters many things that have an impact on it and it is forever changed. Like any journey, one must travel a given path. On that path, there are many roads. Some roads are chosen and others not. Which roads are chosen depends on the end goal.

When you are writing programs, you want your data to make the right decisions. You want your data to do the right thing when it's supposed to. In computer programming, this is called conditional flow.

How do we make data do the right thing? We use conditionals.

Conditionals

A conditional is a fork (or many forks) in the road. Your data approaches a conditional and the conditional then tells the data where to go based on some defined parameters. Conditionals are formed using a combination of if statements and comparison operators (<, >, <=, >=, ==, !=, &&, ||). They are basic logical structures that are defined with the reserved words if, else, elsif, and end. Note that elsif is missing an "e". Enough talking, time to code.

Create a file called conditional.rb and type the following code into it.

# conditional.rb

puts "Put in a number"
a = gets.chomp.to_i

if a == 3
  puts "a is 3"
elsif a == 4
  puts "a is 4"
else
  puts "a is neither 3, nor 4"
end

Here we are using gets to let the user input a number, chomp gets rid of the new line created when the user enters the data, and to_i is a method that can be called on a string to turn it into an integer. We need to convert the input into an integer because gets always gives us a string.

Run this code three times and do the following:

  1. The first time, type in the number 3 and press enter.
  2. The second time, type in the number 4 and press enter.
  3. The third time, type in any number that isn't 3 or 4 and press enter.

You can repeat the third step more than once to see its effect.

What your code is doing is checking, using the == operator you learned previously, to see if the input is equal to the number we have defined. We have effectively controlled the flow of the program by setting conditionals in an if statement. Nice work!

The examples below are all valid Ruby conditionals.

# Example 1
if x == 3
  puts "x is 3"
end

# Example 2
if x == 3
  puts "x is 3"
elsif x == 4
  puts "x is 4"
end

# Example 3
if x == 3
  puts "x is 3"
else
  puts "x is NOT 3"
end

# Example 4: must use "then" keyword when using 1-line syntax
if x == 3 then puts "x is 3" end

Last, because Ruby is such an expressive language, it also allows you to append the if condition at the very end. Example 1 from above could be rewritten like this:

puts "x is 3" if x == 3

Ruby also has a reserved word, unless. It acts as the opposite of if, so you can use it like this:

puts "x is NOT 3" unless x == 3

Comparisons

Let's go over these comparison operators in a little more depth so you can build some more complicated conditional statements. One thing to remember is that comparison operators always return a boolean value. A boolean value is either true or false, nothing else. We'll try them out in irb to see how they work as well.

  1. < - The "less than" symbol. Anything to the left of the symbol has a lower value than anything to the right of the symbol.

  2. > - The "greater than" symbol. Anything to the left of the symbol has a higher value than anything to the right of the symbol.

    # Example using 'less than' and 'greater than'
    
    irb :001 > 4 < 5
    => true
    
    irb :002 > 4 > 5
    => false
     
  3. <= - The "less than or equal to" symbol. Anything to the left of the symbol is less than or equal to anything on the right.

  4. >= - the "greater than or equal to" symbol. Anything to the left of the symbol is greater than or equal to anything on the right.

    irb :001 > 4 <= 5
    => true
    
    irb :002 > 5 >= 5
    => true
    
    irb :003 > 4 >= 5
    => false
    
    irb :004 > 4 >= 3
    => true
    
    irb :005 > 4 >= 4
    => true
     
  5. == - The "is equal to" operator. Anything to the left of the symbol is exactly equal to anything on the right. We talked about this operator earlier in our chapter on variables so it shouldn't be totally foreign.

    irb :001 > 5 == 5
    => true
    
    irb :002 > 5 == 6
    => false
    
    irb :003 > '5' == 5
    => false
     

    We threw that last one in as a reminder that when you are comparing for equality you must be comparing two of the same type or you will always get a false boolean value.

  6. != - The "not equal to" operator. Anything to the left of the symbol is not equal to anything to the right.

    irb :001 > 4 != 5
    => true
    
    irb :002 > 4 != 4
    => false
    
    irb :003 > 4 != 156
    => true
     

Combining Expressions

OK, you're starting to get a decent grasp of conditional flow. It is also possible to combine multiple conditional expressions together to create a more specific scenario. We can do this using the && and || operators. Let's see what they mean.

  1. && - the "and" operator. Expressions to the left and to the right of this operator have to be both true for the entire expression to be evaluated to true.

    irb :001 > (4 == 4) && (5 == 5)
    => true
    
    irb :002 > (4 == 5) && (5 == 5)
    => false
    
    irb :002 > (4 == 5) && (5 == 6)
    => false
     
  2. || - the "or" operator. Either the expression to the left has to be true, or the expression to the right has to be true for the entire expression to be evaluated to true.

    irb :001 > (4 == 4) || (5 == 5)
    => true
    
    irb :002 > (4 == 5) || (5 == 5)
    => true
    
    irb :002 > (4 == 5) || (5 == 6)
    => false
    
  3. ! - the "not" operator. When you add this in front of a boolean expression it will change that boolean value to its opposite.

    irb :001 > !(4 == 4)
    => false
    

What happens here is Ruby first evaluates what is in the parentheses and then the ! operator changes it. We know that 4 == 4 would return true. If we say !true then that returns false. You can think of !true as saying "not true".

Note: When you are combining expressions as we are above, it is helpful to use parentheses to group expressions together. This is helpful for readability and also helps the computer more accurately understand your intention. The computer will evaluate parentheses in normal algebraic order.

Ruby follows an order of precedence when deciding how to evaluate multiple expressions. The following is a list of operations from highest order of precedence (top) to lowest (bottom).

  1. <=, <, >, >= - Comparison
  2. ==, != - Equality
  3. && - Logical AND
  4. || - Logical OR

Knowing this, we can look at the following expression and see how it is evaluated.

if x && y || z
  # do something
end

First the x && y statement will be executed. If that statement is true, then the program will execute the # do something code on the next line. If the x && y statement is false, then the z will be evaluated. If the z is true, the code on the next line will be evaluated. If the z is false, then the code will exit the if statement.

Ternary Operator

Ruby has a nice option for short and concise conditional if statements. The ternary operator is a common Ruby idiom that makes a quick if/else statement easy and keeps it all on one line.

The ternary operator uses a combination of the ? and :.

# Ternary operator example

irb :001 > true ? "this is true" : "this is not true"
 => "this is true"

irb :001 > false ? "this is true" : "this is not true"
 => "this is not true"
 

So, how does this work? You may have inferred that first the computer evaluates what is to the left of the ?. If the expression to the left of ? is true, the code directly to the right of the ? gets executed. If the code on the left of the ? is false, then the code directly to the right of the : gets executed.

Ternary operators definitely come in handy as you start to get more familiar with if statements. If you feel like you are unsure of how this works, play around with it in irb and test some other cases out. Nothing can create familiarity more quickly than good ol' repeated exposure and experimentation.

Case Statement

The final conditional flow structure that we want to talk about is called a case statement. A case statement has similar functionality to an if statement but with a slightly different interface.

Case statements use the reserved words case, when, else, and end. You create one by first defining a case and then evaluating the value of the case and what operation to complete if that case is true. As always, talking about this stuff is much harder than simply observing how the code behaves. Let's create a file called case_statement.rb to play with some case statements and see how they work.

# case_statement.rb

a = 5

case a
when 5
  puts "a is 5"
when 6
  puts "a is 6"
else
  puts "a is neither 5, nor 6"
end

This example is sort of a modified version of the if/elsif/else statement that we created earlier. So you can see how these two are similar.

You can also save the result of a case statement into a variable. Let's refactor the code above to do just that. This way we don't have to write puts so many times.

# case_statement.rb <-- refactored

a = 5

answer = case a
  when 5
    "a is 5"
  when 6
    "a is 6"
  else
    "a is neither 5, nor 6"
  end

puts answer

You don't necessarily have to give case an argument either. You could do the following.

# case_statement.rb <-- refactored with no case argument

a = 5

answer = case
  when a == 5
    "a is 5"
  when a == 6
    "a is 6"
  else
    "a is neither 5, nor 6"
  end

puts answer

So you can see that there are lots of uses for case statements and they can be very powerful tools when you are writing Ruby programs. Remember, if you're uncomfortable with these, spend some time modifying them and watching how they respond to the changes you make. Test their boundaries to see what they are capable of. Curiosity will serve you well in your journey to learning Ruby. There is much to discover!

True and False

Notice that after if and elsif we have to put an expression that evaluates to a boolean value: true or false. In Ruby, you could even write code like this:

a = 5
if a
  puts "how can this be true?"
else
  puts "it is not true"
end

The output is "how can this be true?". In Ruby, every expression evaluates to true when used in flow control, except for false and nil. Try the code above and give a values of 0, ''(empty string) and even the string 'false' to see the result yourself!

Because of this, we could even write code like this:

if x = 5
  puts "how can this be true?"
else
  puts "it is not true"
end

The above code is not testing whether x is equal to "5". It's assigning the variable x the value of "5", which will always evaluate to true. Unfortunately, that looks very similar to if x == 5, which is testing whether x is equal to "5". Be careful when reading or writing Ruby; its expressiveness can also be a source of many subtle bugs.

Summary

This chapter covered booleans, comparisons and the ability to control the flow of code execution with conditionals. These are some of the fundamental tools that you'll carry with you as a Ruby developer. We've got more exercises to drill these skills into your head and fingers!

Exercises

  1. Write down whether the following expressions return true or false. Then type the expressions into irb to see the results.

    1. (32 * 4) >= 129
    2. false != !true
    3. true == 4
    4. false == (847 == '874')
    5. (!true || (!(100 / 5) == 20) || ((328 / 4) == 82)) || false
    

    Solution

    1. false
    2. false
    3. false
    4. true
    5. true
    

    Video Walkthrough

    Please register to play this video
  2. Write a method that takes a string as argument. The method should return a new, all-caps version of the string, only if the string is longer than 10 characters. Example: change "hello world" to "HELLO WORLD". (Hint: Ruby's String class has a few methods that would be helpful. Check the Ruby Docs!)

    Solution

    # caps_method.rb
    
    def caps(string)
      if string.length > 10
        string.upcase
      else
        string
      end
    end
    
    puts caps("Joe Smith")
    puts caps("Joe Robertson")
    

    Video Walkthrough

    Please register to play this video
  3. Write a program that takes a number from the user between 0 and 100 and reports back whether the number is between 0 and 50, 51 and 100, or above 100.

    Solution

    # evaluate_num.rb
    
    puts "Please enter a number between 0 and 100."
    number = gets.chomp.to_i
    
    if number < 0
      puts "You can't enter a negative number!"
    elsif number <= 50
      puts "#{number} is between 0 and 50"
    elsif number <= 100
      puts "#{number} is between 51 and 100"
    else
      puts "#{number} is above 100"
    end
    

    Video Walkthrough

    Please register to play this video
  4. What will each block of code below print to the screen? Write your answer on a piece of paper or in a text editor and then run each block of code to see if you were correct.

    1. '4' == 4 ? puts("TRUE") : puts("FALSE")
    
    2. x = 2
       if ((x * 3) / 2) == (4 + 4 - x - 3)
         puts "Did you get it right?"
       else
         puts "Did you?"
       end
    
    3. y = 9
       x = 10
       if (x + 1) <= (y)
         puts "Alright."
       elsif (x + 1) >= (y)
         puts "Alright now!"
       elsif (y + 1) == x
         puts "ALRIGHT NOW!"
       else
         puts "Alrighty!"
       end
    

    Solution

    1. "FALSE"
    2. "Did you get it right?"
    3. "Alright now!"
    

    Video Walkthrough

    Please register to play this video
  5. Rewrite your program from exercise 3 using a case statement. Wrap this new case statement in a method and make sure it still works.

    Solution

    # evaluate_num.rb
    
    def evaluate_num(number)
      case
      when number < 0
        puts "You can't enter a negative number!"
      when number <= 50
        puts "#{number} is between 0 and 50"
      when number <= 100
        puts "#{number} is between 51 and 100"
      else
        puts "#{number} is above 100"
      end
    end
    
    # or
    
    def evaluate_num(number)
      case number
      when 0..50
        puts "#{number} is between 0 and 50"
      when 51..100
        puts "#{number} is between 51 and 100"
      else
        if number < 0
          puts "You can't enter a negative number!"
        else
          puts "#{number} is above 100"
        end
      end
    end
    
    puts "Please enter a number between 0 and 100."
    number = gets.chomp.to_i
    
    evaluate_num(number)
    

    Video Walkthrough

    Please register to play this video
  6. When you run the following code...

    def equal_to_four(x)
      if x == 4
        puts "yup"
      else
        puts "nope"
    end
    
    equal_to_four(5)
    

    You get the following error message..

    exercise.rb:8: syntax error, unexpected end-of-input, expecting keyword_end
    

    Why do you get this error and how can you fix it?

    Solution

    You get this error because the end in the code above gets matched with the if..else statement. The error message is telling us that the interpreter was expecting the keyword end to close off our equal_to_four method, that end was not found.

    Video Walkthrough

    Please register to play this video