A Programming Primer for Counting and Other Unconventional Tasks

If and Else

Breaking out from a narrow, straight path of program flow.

We saw how variables let us refer to data not yet known to us when we're writing a program.

Similarly, if/else statements allow us to create different paths for our program to take depending on what happens during the program's execution.

if my_bank_account_balance > 50.00
   puts "I'm eating steak!"
else
   puts "I'm eating ramen :("
end   

Tomorrow, you might plan to go out for steak dinner if your tax refund arrives in the mail. If it doesn't, then you'll plan on eating ramen. Likewise, in your programs, you plan for hypothetical situations. Such as: what happens if the website you want to scrape is down?

This chapter looks at how to put some branches in our program so it no longer flows in a single, narrow path.

The if

The if statement is how we create a branch in our program flow. The if statement includes a true-or-false expression:

if 4 == 2 + 2
   puts "The laws of arithmetic work today"
end   

If that expression evaluates to true, then the Ruby interpreter will execute the puts statement that follows the if statement. However, if the expression's result is false, then the Ruby interpreter skips the puts.


def even_or_odd(val)
   if val % 2 == 0
      return "#{val} is even"
   end
   
   if val % 2 == 1
      return "#{val} is odd"
   end
end
   

Try out that code. Pass in a number to the even_or_odd method that has a remainder of 1 when divided by 2 – i.e. an odd number. The code inside the first if statement won't execute. Here's a breakdown of the new terms:

if
This keyword indicates that the branch of code following it will execute if the if condition is true
==
Two consecutive equals signs is the operator for an equality test. When the value on the left of == is equal to the right-side value, then the result is true.
end
This keyword in this context indicates the end of the branch. If the if statement was false, this is where the program resumes, as it skips the branch of code following the if.

In the code example above, the method first tests to see if the remainder of val – when divided by 2 – is equal to 0. So, if val % 2 is 0, then the method returns "#{val} is even".

However, if val is odd, then the first if test fails because the remainder is non-zero. The program moves on to the next if statement: val is tested to see if its remainder is 1 when divided by 2. So, if val % 2 is equal to 1, then the method returns "#{val} is odd".

When you write the even_or_odd method, you can't know what number the user will pass in for the argument. The if statement gives your program the ability to respond with the appropriate answer.

Booleans: the truth about true and false

The words true and false have special meaning in programming languages. In Ruby, they have the datatypes of TrueClass and FalseClass, respectively.

The specific class names aren't important to remember. The main point is that true and false – note the lack of String-denoting quotes – are the answers to comparison tests.

Such as: is 10 times 2 greater than 0?.

10 * 2 > 0

Or: is -1 equal to 1?

-1 == 1

The results, respectively, are the values true and false.

These two values – true and falseare not Strings. Test this out for yourself:


true == "true"   #   false
false == false   #   true      
   

A common mistake is to use a single equals sign = when a double equals sign == is called for. Remember that the single = assigns a value to a variable. The double == is used to test for equality.

In other words:

=
set equal to
==
is equal to?

Virtually every value – besides false and nil – evaluates as true by an if statement. Ergo:


x = "To be or not to be"
if x
   puts "x exists!"
end
#=> x exists!

if x == 1
   puts "x is equal to 1"
end
#=> [branch does not execute]   

if x = 1
   puts "The variable x is now number 1!"
end
#=> The variable x is now number 1!   

puts x   #=>   1
      

In the first if statement, we are saying: if there is something in the variable x... Since we assigned "To be or not to be" to x, the answer is always true.

In the second if statement, because we used a single equals sign, the if statement actually uses the assignment operator. So the expression first assigns the value of 1 to x. The if statement then evaluates whether anything is in x.

The answer to if 1 is always true, so we get the unexpected output as above.

Boolean expressions

Besides equality, testing for inequalities can be done with the following comparison and logical operators:

==
equal to
!=
not equal to
<
less than
>
greater than
<=
less than or equal to
>=
greater than or equal to

Comparison expressions are often combined:

||
OR
&&
AND

An example:


num = 100
if num <= 100 || num % 2 == 1
    puts "Less or equal to 100 or odd"
end   
 
#=> Less or equal to 100 or odd
    
Negation

To test whether one value is not equal to another, use the exclamation sign ! and a single equals sign =


num = 12
if num != "Twelve"
    puts "Not twelve"
end        
#=> Not twelve

To negate an entire expression, use the exclamation sign ! in front of the expression. Use parentheses to specify the order of evaluation:


puts !(6 == 7)  #=> true
        

Note that is the same as:


puts 6 != 7 #=> true
            

However, sans parentheses, it is a different expression:


puts !6 == 7    #=> false      
    

Without the parentheses, the order of operations is different. Here, the ! operator negates the 6, making it equivalent to false. Then that value is tested for equality with 7.

Thus, this is what the above expression is actually comparing:


puts false == 7    #=> false      
    

Why is this? Because the ! operator takes precedence. In arithmetics, you had to learn that multiply operations take effect before addition operations, i.e. order of operations. Programming languages have their own rules, which are just a bit of a memorization. When in doubt, use parentheses.

Nothing, zilch, nada: nil

In Ruby, the object nil is Ruby's representation of nothing. Yes, it has a class (NilClass) and several methods common to all objects. But its main purpose is to indicate nothingness.

Nothingness is not the same as 0, nor an empty string like "". It is just nothing. In other languages, it's called referred to as null.

In terms of conditional statements, nil is the only value other than false to be considered false:


if puts("puts is a method returning nil")
   # program will never go here
end

nil == false   #   false; nil is not technically the same as false
   

All objects, including nil itself, has the nil? method, which tests whether or not an object is nil.


puts("hello world").nil?   #   true
42.nil?   #   false
0.nil?   #   false
"".nil?   #   false
nil.nil?   #   true      
   

There haven't been many situations yet in which we deal with nil. But nil values will be a commonplace when we deal with collections and try to access items out of a collection's range.

Going elsewhere

Let's tighten up the even_or_odd method from the previous section:


def even_or_odd(val)
   if val % 2 == 0    # any number divided by 2 is even if remainder is 0
      "#{val} is even"
   else    # if not even, then must be odd
      "#{val} is odd"
   end
end

The new keyword is else and leads to a path for a program to resume if the conditions for an if statement weren't met. An else statement must come after an if statement. Or else, what would the else be an alternative to?

Notice how there are no return statements. Remember that a Ruby method by default will return the value in its final line. When the argument val is even, the code including and following the else statement doesn't exist, as far as Ruby is concerned.

So "#{val} is even" will be the return value of even_or_odd, without an explicit return statement in the method's final line.

elsif – when you need more than an either/or

So you've provided your client with a nifty method of informing him whether any number he enters is even or odd. So your client tries this:


even_or_odd(4.2)   #=>   "4.2 is odd"

That doesn't seem right, does it? That decimal number seems more even than odd. In fact, "even" and "odd" are only valid for integers. If we want to designate that decimal numbers are neither even or odd, we need a third conditional branch:


def even_or_odd(val)
   if !(val.is_a? Fixnum)
      "#{val} is not an integer"
   elsif val % 2 == 0    
      "#{val} is even"
   else             
      "#{val} is odd"
   end
end
         

A few new things here:

elsif
This indicates an alternative to the preceding if – and any other preceding elsif statements. So, you start off with the if, have any number of elsif alternatives, and end with an else to capture all the leftover possibilities. In other languages, elsif is spelled out as: else if
!
The exclamation mark, when used in front of a Boolean statement, negates the Boolean. So, !true is false. !false is true. And !!true is true (double negation). Sometimes, coders will use parentheses for easier readability, e.g. !(my_variable)
is_a?
This is a Ruby method that can be called by any object. It returns true if the object belongs to the same Class as the Class you pass as an argument intois_a?. The question mark here is just part of the method name, as if it were a regular alphanumeric character.

We had a brief introduction about classes and the class method back in the Strings chapter. You can read this supplemental chapter for more information on object-oriented programming concepts but we won't delve into it here.

It's enough for now to understand that our if statement tests to see if val has a class of Fixnum.

Exercise: Booleans

Implement the following functionality using if/else statements and booleans.

  1. Output "Big" if x is greater than 10
  2. Output "Small" if x is less than or equal to 10, but not negative
  3. If x is greater than 14 but less than 100, and x is even, output "Dog". If it is greater than or equal to 100 and it is less than or equal to 1000, or, if x is odd, then output "Elephant". Otherwise, output "Bananas"
Solution

Exercise 1:

if val > 10
   puts "Big" 
end  

Exercise 2:

if val > 10 && val <= 0    
   puts "Small" 
end

Exercise 3:


if val > 14 && val < 100 && val % 2==0 
   puts "Dog"
elsif (val >= 100 && val < 1000) || val % 2==1
   puts "Elephant"
else
   puts "Bananas"
end   

Variations on if

Inline if statements

Ruby provides a neat inline form for if/else statements, when the logic in your script is simple:

num = -10
num += -1 if num < 0
puts num   
#=> -11

As opposed to:

num = -10
if num < 0
   num += -1
end
puts num   
#=> -11
When to use if =

An experienced coder may choose to use a single equals sign – i.e. the assignment operator – in the following situation:

If the value on the right side of = is the return value of a method or expression – and if that method either returns something or nothing at all.

In the latter case, when the return value is nil, the expression is considered to be false:

# remember that puts returns nil
if x = (puts 'hello world') 
   puts "Successful assignment. x is now #{x}"
end
#=> hello world
# the code block does not execute   
In the former case, it is considered to be true:
# upcase returns a string
if x = ('hello world'.upcase) 
   puts "Successful assignment. x is now: #{x}"
end

#=> Successful assignment. x is now: HELLO WORLD

In both cases, the value is conveniently assigned to the variable on the left.

require 'open-uri'
if page = open("http://google.com")         
   puts "Google's homepage currently contains the following text: #{page.read}"
end

This is more concise than:

require 'open-uri'
page = open("http://google.com")            
if page
   puts "Google's homepage currently contains the following text: #{page.read}"
end

This use of if x = is handy. But it can be easily misunderstood when you review your code. Stick with the verbose version for now.

Logic and flow

One of the most common novice mistakes with if/else is thinking that multiple if statements are equivalent to using elsif and else.

Think of if as creating a two-pronged fork in the road. The script has the option of taking or avoiding that side-path created by the if. If it does go the if, when the script reaches the end, it continues on linearly until the next fork.

When an elsif or else follows an if statement, it creates a three-prong-or-more fork in the road. The script has more choices but can only go down one of these routes.

Here's a food metaphor:

  • Restaurant A offers you the choice of roast chicken, or ribeye, or salad, or soup.
  • Restaurant B offers you a multi-course meal: soup, and/or salad, and/or chicken and/or ribeye.

What each restaurant serves you is dependent on how hungry you feel represented by a number from 1 to 10.

Here's how Restaurant A's service would be implemented in code:


def restaurant_a(how_hungry_are_you)
   meal = ""
   if how_hungry_are_you >= 9
      meal = "Ribeye" 
   elsif how_hungry_are_you >= 7
      meal = "Chicken"
   elsif how_hungry_are_you >= 4
      meal = "Soup"
   else
      meal = "Salad"
   end   
end

puts restaurant_a(10)   #=>   "Ribeye"
puts restaurant_a(4)   #=>   "Soup"
            
      

Here is the code for Restaurant B:


def restaurant_b(how_hungry_are_you)
   meal = ""
   meal += "Ribeye " if how_hungry_are_you >= 9
   meal += "Chicken " if how_hungry_are_you >= 7
   meal += "Soup " if how_hungry_are_you >= 4
   meal += "Salad " if how_hungry_are_you >= 1               
end

puts restaurant_b(3)   #=>   "Salad "
puts restaurant_b(4)   #=>   "Soup Salad "
puts restaurant_b(10)   #=>   "Ribeye Chicken Soup Salad "
   

The branch logic for Restaurant B is more compact but its service is fundamentally different than Restaurant A's. Restaurant A gives you one meal depending on how hungry you are. A 10 or above will net you a ribeye steak, at most.

At Restaurant B, however, a 10 gets you everything. Because 10 is not only greater than the criteria for B's ribeye (greater or equal to 9), but it satisfies the hunger criteria for all the other meal choices.

Exercise: Rewrite Restaurant A's code

Restaurant A's service model might make more sense to you. But you like the compactness of B's code, with its use of the inline if statement. So, rewrite A's method using only if statements.

Hint: Use the "AND" operator to join one or more true/false conditions

Solution

Here's how to write Restaurant A's code using only if statements:


def restaurant_a2(how_hungry_are_you)
   meal = "Ribeye" if how_hungry_are_you >= 9 
   meal = "Chicken" if how_hungry_are_you < 9 && how_hungry_are_you >= 7
   meal = "Soup" if how_hungry_are_you < 7 && how_hungry_are_you >= 4
   meal = "Salad" if how_hungry_are_you < 4 && how_hungry_are_you >= 1
   return meal               
end
            

With the extra conditions we've imposed on each if statement, the script can only return one of the choices.

And while this took fewer lines of code, it's not necessarily faster than the first implementation of Restaurant A. In the first version, as soon as the script satisfies one of the conditions, it moves on to the end of the if/else block.

In the second version of A, the script has to test how_hungry_are_you at each if statement, even though we want the method to return just one meal choice. But what happens if another programmer adjusts the criteria for each choice and doesn't double-check his/her math?

So the upshot: Readability over brevity. Using a combniation of if/elsif/else statements are the way to go here.

Exercise: Returning to return

In the implementation of restaurant_a2, what would happen if we omitted the return statement in the last line. What is the result of restaurant_a2(10)?

Solution

The method call restaurant_a2(10) would return nil instead of "Ribeye"

Why? Because as we've learned in the methods chapter, a Ruby method will return the value of the last line by default if no return command is explicitly used.

So, when omitting the return statement, the last line in restaurant_a2 is always meal = "Salad" if .... And even though 10 satisfies the "Ribeye" branch, the script still progresses through to the final "Salad" test.

Since 10 is not less than 4, nothing happens. Thus, the value of that line is nil.

The FizzBuzz Game

Conditional statements allow for both incredibly useful program functionality and crippling bugs. Let's test how well you understand them by tackling a technical programming interview question that, according to developer Imran Ghor, has stumped "the majority of comp sci graduates."

It's called the FizzBuzz game and it is frequently invoked during discussions about the general competency among programmers. There are a variety of ways to solve it and you've learned enough to answer it competently.

Exercise: Play FizzBuzz

In his blog post, "Using FizzBuzz to Find Developers who Grok Coding," Imran Ghory lays out this description of the FizzBuzz game:

Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”.

There are a couple things you have to know to do this efficiently, besides conditional statements:

  • Simple arithmetic: how do you find if a number is divisible by two different numbers?
  • The for loop. We cover loops in the next chapter. For now, type this code into irb/your text editor, and observe the output:

    for num in 1..100
       puts num
    end

    You should see the numbers 1 to 100 printed out on each line. You may not know about loops, but it's enough to know that the above loop executes whatever is in between the for and the end 1 through 100 times. And that the num variable refers to the current iteration.

So write out the logic needed to make the printout in each iteration match the FizzBuzz requirements.

The easy solution

The following works. It won't win any prizes for creativity, but it passes the test:


for i in 1..100
   if i % 15 == 0
      puts "FizzBuzz"
   elsif i % 3 == 0
      puts "Fizz"
   elsif i % 5 == 0
      puts "Buzz"
   else
      puts i
   end
end

The output:

   1
   2
   Fizz
   4
   Buzz
   Fizz
   7
   8
   Fizz
   Buzz
   11
   Fizz
   13
   14
   FizzBuzz
   16
   17
   Fizz
   19
   Buzz
   Fizz
   22
   23
   Fizz
   Buzz
   26
   ... and so forth   

It's an easy problem that requires knowing the fundamentals, like asking a sports reporter applicant to write a recap of a baseball game. But it's also deceptively easy. Let's look at the common ways you could mess this up.

A reordered solution?

Maybe you thought the previous solution was half-backwards; why not deal with the relevant numbers – 3, 5, and 15 – in numerical order?


for i in 1..100
   if i % 3==0
      puts "Fizz"
   elsif i % 5 == 0
      puts "Buzz"
   elsif i % 15 == 0
      puts "FizzBuzz"
   else
      puts i
   end
end
                           

But as you learned in this chapter, the interpreter will exit out of the if block at the first set of conditions that are met. So, in the above code, the result will be:

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
Fizz
16
...

Where "FizzBuzz" should've been printed, there is only Fizz.

A shorter solution?

What if you wanted to tighten up the solution's code and tried this:


for i in 1..100
   puts "FizzBuzz" if i % 15 == 0
   puts "Fizz" if i % 3 == 0
   puts "Buzz" if i % 5 == 0
   puts i
end
            

You get points for utilizing Ruby's inline if style. But your particular branch flow fails at i==15, because it will print: "FizzBuzz", "Fizz", and "Buzz" and "15", because 15 satisfies all three separate if conditions and will print out at the fourth line puts i:

1
2
Fizz
3
4
Buzz
5
Fizz
6
7
8
Fizz
9
Buzz
10
11
Fizz
12
13
14
FizzBuzz
Fizz
Buzz
15   
   

In fact, this solution fails at any multiple of 3 or 5, as the requirements ask that "Fizz" and/or "Buzz" are printed in lieu of i.

The takeaway: Using three if conditions is not the same as a triple if/elsif branch.

A cleaner solution

There's not much more room – nor need, in a real-life Ruby environment – to optimize the original solution. But it's good practice to identify redundancy.

In the code of our original solution, there are two times in which we print "Fizz" and "Buzz":

  1. if i % 15 == 0
  2. i % 3 == 0 or i % 5 == 0

With a slight reorganization, we can move the most specialized conditional, i % 15 == 0, to the very end, and take advantage of the output from the previous branches:


for i in 1..100
   str = ''
   str += 'Fizz' if i%3==0
   str += 'Buzz' if i%5==0
   puts (str == '' ? i : str)
end
            

The last line uses a ternary operation, which is a sort of inline if/else style that many languages have. The equivalent if/else statement would be:


# puts (str == '' ? i : str)
# is the same as:
if str == ''
   puts i
else
   puts str
end

It turns out that this solution may be more compact and easier to read. But it ends up taking longer to execute. Not only are more comparisons needed, but there's the cost of adding the strings together. So even with a cleaner look, it may not be the ideal solution.