Chapter 5. Conditionals and Recursion

The main topic of this chapter is the if statement, which executes different code depending on the state of the program. With the if statement we’ll be able to explore one of the most powerful ideas in computing, recursion.

But we’ll start with three new features: the modulus operator, boolean expressions, and logical operators.

Recursion

It is legal for a function to call itself. It may not be obvious why that is a good thing, but it turns out to be one of the most magical things a program can do. Here’s an example:

def countdown(n):
    if n <= 0:
        print('Blastoff!')
    else:
        print(n)
        countdown(n-1)
        

If n is 0 or negative, countdown outputs the word, “Blastoff!”. Otherwise, it outputs n and then calls itself, passing n-1 as an argument.

Here’s what happens when we call this function with the argument 3:

countdown(3)
        
3
2
1
Blastoff!
        

The execution of countdown begins with n=3, and since n is greater than 0, it displays 3, and then calls itself…

The execution of countdown begins with n=2, and since n is greater than 0, it displays 2, and then calls itself…

The execution of countdown begins with n=1, and since n is greater than 0, it displays 1, and then calls itself…

The execution of countdown begins with n=0, and since n is not greater than 0, it displays “Blastoff!” and returns.

The countdown that got n=1 returns.

The countdown that got n=2 returns.

The countdown that got n=3 returns.

A function that calls itself is recursive. As another example, we can write a function that prints a string n times:

def print_n_times(string, n):
    if n > 0:
        print(string)
        print_n_times(string, n-1)
        

If n is positive, print_n_times displays the value of string and then calls itself, passing along string and n-1 as arguments.

If n is 0 or negative, the condition is false and print_n_times does nothing.

Here’s how it works:

print_n_times('Spam ', 4)
        
Spam 
Spam 
Spam 
Spam 
        

For simple examples like this, it is probably easier to use a for loop. But we will see examples later that are hard to write with a for loop and easy to write with recursion, so it is good to start early.

Debugging

When a syntax or runtime error occurs, the error message contains a lot of information, but it can be overwhelming. The most useful parts are usually:

  • What kind of error it was, and

  • Where it occurred.

Syntax errors are usually easy to find, but there are a few gotchas. Errors related to spaces and tabs can be tricky because they are invisible and we are used to ignoring them:

x = 5
y = 6
        
Cell In[50], line 2
  y = 6
  ^
IndentationError: unexpected indent
        

In this example, the problem is that the second line is indented by one space. But the error message points to y, which is misleading. Error messages indicate where the problem was discovered, but the actual error might be earlier in the code.

The same is true of runtime errors. For example, suppose you are trying to convert a ratio to decibels, like this:

import math
numerator = 9
denominator = 10
ratio = numerator // denominator
decibels = 10 * math.log10(ratio)
        
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[52], line 5
      3 denominator = 10
      4 ratio = numerator // denominator
----> 5 decibels = 10 * math.log10(ratio)

ValueError: math domain error
        

The error message indicates line 5, but there is nothing wrong with that line. The problem is in line 4, which uses floor division instead of floating-point division—as a result, the value of ratio is 0. When we call math.log10, we get a ValueError with the message math domain error, because 0 is not in the “domain” of valid arguments for math.log10, because the logarithm of 0 is undefined.

In general, you should take the time to read error messages carefully, but don’t assume that everything they say is correct.

Exercises

Ask a Virtual Assistant

  • Ask a virtual assistant, “What are some uses of the modulus operator?”

  • Python provides operators to compute the logical operations and, or, and not, but it doesn’t have an operator that computes the exclusive or operation, usually written xor. Ask an assistant “What is the logical xor operation and how do I compute it in Python?”

In this chapter, we saw two ways to write an if statement with three branches, using a chained conditional or a nested conditional. You can use a virtual assistant to convert from one to the other. For example, ask a virtual assistant, “Convert this statement to a chained conditional”:

if x == y:
    print('x and y are equal')
else:
    if x < y:
        print('x is less than y')
    else:
        print('x is greater than y')
        
x is less than y
        

Ask a virtual assistant, “Rewrite this statement with a single conditional”:

if 0 < x:
    if x < 10:
        print('x is a positive single-digit number.')
        
x is a positive single-digit number.
        

See if a virtual assistant can simplify this unnecessary complexity:

if not x <= 0 and not x >= 10:
    print('x is a positive single-digit number.')
        
x is a positive single-digit number.
        

Here’s an attempt at a recursive function that counts down by two:

def countdown_by_two(n):
    if n == 0:
        print('Blastoff!')
    else:
        print(n)
        countdown_by_two(n-2)
        

It seems to work:

countdown_by_two(6)
        
6
4
2
Blastoff!
        

But it has an error. Ask a virtual assistant what’s wrong and how to fix it. Paste the solution it provides here and test it.

Exercise

If you are given three sticks, you may or may not be able to arrange them in a triangle. For example, if one of the sticks is 12 inches long and the other two are 1 inch long, you will not be able to get the short sticks to meet in the middle. For any three lengths, there is a test to see if it is possible to form a triangle:

If any of the three lengths is greater than the sum of the other two, then you cannot form a triangle. Otherwise, you can. (If the sum of two lengths equals the third, they form what is called a “degenerate” triangle.)

Write a function named is_triangle that takes three integers as arguments, and that prints either “Yes” or “No,” depending on whether you can or cannot form a triangle from sticks with the given lengths. Hint: use a chained conditional.