Chapter 11

Python Loops

Subsections of Python Loops

While Loops

YouTube Video

Resources

The first type of loop to explore in Python is the while loop. A while loop uses a Boolean expression, and will repeat the code inside of the loop as long as the Boolean expression evaluates to True. These loops are typically used when we want to repeat some steps, but we aren’t sure exactly how many times it must be done. Instead, we typically know that there is some condition that must be True while we repeat the code, and once it turns False we can stop looping and continue with the program.

The general syntax for a while loop in Python is shown here:

while <boolean expression>:
    <block of statements>

Notice that this syntax is very similar to an if statement. We start with the keyword while, followed by some sort of a <boolean expression> that will evaluate to either True or False. The Boolean expression in Python does not need to be placed inside of parentheses, unlike in pseudocode. After the Boolean expression is a single colon :. Then, starting on the next line and indented one level is a <block of statements> that will be executed when the Boolean expression evaluates to True. Once again, just like in an if statement, we know which statements are part of the block within a while loop based solely on indentation.

Just like in pseudocode, it is possible for the Boolean expression to evaluate to False initially, which will bypass the loop entirely and never execute the code inside of it. Likewise, if we aren’t careful, we can also write a loop where the Boolean expression will always be True, and the loop will run infinitely, causing our program to lock up and never terminate.

Code Tracing Example

To truly understand how a while loop works in Python, let’s go through a simple code tracing example in Python Tutor. Consider the following Python program:

def main():
    x = int(input("Enter a number from 0 to 100: "))
    total = 0
    while total % 100 != x:
        total = total + 9
    print("The smallest multiple of 9 that ends in {} is {}".format(x, total))


main()

See if you can figure out what this program does before moving on!

As always, you can copy and paste this code in the tutor.py file in Codio, or click this Python Tutor link.

When we load this code in Python Tutor, we should see the usual default state:

Tutor 1 Tutor 1

The first couple of steps will simply find the main() method and record it in the global frame, and then call the main() method from the bottom of the program

Tutor 3 Tutor 3

The first line of the program will prompt the user for input and store it in the variable x. For this example, let’s assume the user inputs the string "27".

Tutor 4 Tutor 4

Therefore, we’ll store the value $27$ in the variable x. The next line will store the value $0$ in the variable total, as shown here:

Tutor 6 Tutor 6

At this point, we’ve reached the beginning of the while loop. So, we’ll need to determine if the Boolean expression evaluates to True or False. In this case, the value of total % 100 is equal to $0$, so it is not equal to the value stored in x. Therefore, the Boolean expression is True, and we should enter the loop.

Tutor 7 Tutor 7

Inside of the loop, we are simply adding $9$ to the value in total. So, we’ll see that variable is updated, and the execution pointer goes back to the top of the loop.

Tutor 8 Tutor 8

Now that we are back at the top of the loop, we need to check the Boolean expression again. This time the value of total % 100 is $9$, which still isn’t equal to the value stored in x, so we’ll enter the loop again.

Tutor 9 Tutor 9

We’ll add $9$ to the total here, and jump to the top again.

Tutor 10 Tutor 10

Once again, we will check the Boolean expression and see that it is still True, so we’ll enter the loop and add $9$ to the total yet again before repeating the process from the top of the loop. At this point, we should see this state in Python tutor.

Tutor 12 Tutor 12

When we compute total % 100, we get the value $27$, which is the same as the value stored in x. This will make the Boolean expression evaluate to False, so we can skip the loop and jump to the bottom of the program.

Tutor 13 Tutor 13

Here, we are simply printing out the output, and then the program will terminate.

Tutor 14 Tutor 14

A full animation of this program’s execution in Python Tutor is shown here.

Tutor Animation 7 Tutor Animation 7

Tracing the execution of a while loop is quite simple, especially with tools such as Python Tutor to help us make sure we’re updating the variables in the correct order.

Subsections of While Loops

For Loops

YouTube Video

Resources

Python also includes a second type of loop that is very useful, the for loop. Just like in pseudocode, a for loop is used when we want to repeat the steps a certain number of times. However, in Python, we can’t just say that we want to repeat something $10$ times. Instead, we use the built-in range() function in Python to generate a list of numbers that we use in our loop. Then, our for loop will repeat once for each number in the list, and we can even access that number using an iterator variable

note-1

We’ll learn more about lists in Python in a later lab. For now, we’re just going to use the range() function to generate them for use with for loops.

Range Function

The range() function can be used in three different ways, depending on the number of arguments given when calling the function:

  • range(stop) - with a single argument, the range() function will generate a list of numbers that begins at $0$ and stops before it reaches the stop value. For example, range(10) will generate a list of numbers from $0$ through $9$. This is great, since there will be $10$ numbers in total, so a for loop using range(10) will repeat $10$ times.
  • range(start, stop) - with two arguments, the range() function will generate a list of numbers that begins at start and stops before it reaches the stop value. So, range(3,8) will generate the list [3, 4, 5, 6, 7]. There will be stop - start numbers in the range.
  • range(start, stop, step) - with three arguments, the range() function will generate a list of numbers that begins at start, increases by step each time, and stops before the stop value. Therefore, range(0, 10, 2) will generate the list [0, 2, 4, 6, 8]. We can also use a negative value for step to count backwards. So, range(5, 0, -1) will generate the list [5, 4, 3, 2, 1].

You can read more about the Python range() function in the Python Documentation

For Loops

The general structure of a for loop in Python is shown here:

for <iterator variable> in <list>:
    <block of statements>

The first time we reach a for loop, we’ll keep track of the <list> that we’ll be iterating over. In general, once you start repeating in a for loop, the <list> cannot be changed.

If the list contains at least one item, then we’ll store the first item in the list in the <iterator variable> and enter the for loop to execute the code in the <block of statements> When we reach the end of the block, we’ll jump back to the top and check to see if the <list> contains another item. If so, we’ll store that item in the <iterator variable> and execute the code in the <block of statements> again.

This process will repeat until we’ve used every item in the <list>. Then, we’ll jump to the end of the for loop and continue the program from there.

Code Tracing Example

To really see how a for loop works in Python, let’s use Python Tutor to trace through a quick sample program. Consider this Python program:

def main():
    x = int(input("Enter a number: "))
    line = ""
    for i in range(x):
        line = line + "*"
        print(line)


main()

Before working through the code in Python Tutor, see if you can determine what this program does just by reading it!

To step through the code, we can load it into Python Tutor by placing it in the tutor.py file in the python folder on Codio, or clicking this Python Tutor link.

We’ll start with the usual default state as shown here:

Tutor 1 Tutor 1

The next two steps will find the main() function in the code and store it in the global frame, and then it will find the main() function call at the bottom of the file. So, once it enters the main() function, we should be at this state:

Tutor 3 Tutor 3

The next line will read the input from the user. For this example, let’s assume the user inputs the string "5":

Tutor 4 Tutor 4

This means that we’ll store the number $5$ in the variable x. The program will also create an empty string value in the line variable, which we’ll use to generate output. At that point, we should have reached the beginning of the for loop.

Tutor 6 Tutor 6

Behind the scenes, Python will generate a list of numbers based on the range() function. Since the range() function is called with a single argument, we know it will be a list of numbers starting at $0$ and ending before it reaches the value in x, which is $5$. So, the list will contain the numbers [0, 1, 2, 3, 4], and it will cause the for loop to repeat $5$ times. For the first iteration of the loop, Python will store the first number in the list, $0$, in the iterator variable i. In programming, we typically use the variable name i to represent our iterator variable. So, once the program has entered the loop, we should see this state:

Tutor 7 Tutor 7

Inside of the loop, the first statement will update the line value by adding an asterisk * to the end of the string. So, after this statement is executed, the line variable will store a string containing a single asterisk.

Tutor 8 Tutor 8

The second statement will print the line variable to the output.

Tutor 9 Tutor 9

At this point, we’ve reached the end of the for loop, so our execution pointer jumps back up to the top of the loop. Then, we check to see if the list we are iterating through contains more values. Again, this list isn’t shown in Python tutor, so we have to mentally keep track, but we can see the current value in i, and we know what the list contains by looking at the range() function. So, we’ll update i to store the value $1$, and repeat the loop once again.

Tutor 10 Tutor 10

Just like before, this iteration will add an asterisk to the string in the line variable, and then print that to the output before repeating once again.

Tutor 12 Tutor 12

There are still more items in the list, so we’ll update i to the value $2$ and enter the loop:

Tutor 13 Tutor 13

In the loop, we’ll update line to contain one more asterisk, and then print it before looping back to the top.

Tutor 15 Tutor 15

The list still isn’t empty, so we’ll update i to be $3$ this time:

Tutor 16 Tutor 16

And then we’ll update line and print it.

Tutor 18 Tutor 18

By now, we should have a pretty good idea of what this loop does. There’s one more item in the list, so we’ll update i to be $4$:

Tutor 19 Tutor 19

Inside the loop, we’ll add one more asterisk to line and then print it.

Tutor 21 Tutor 21

Finally, we’re back at the top of the loop. Since there are no more items left in the list, we can jump to the bottom of the for loop and continue the program from there. In this case, there’s no more code left in the main() function, so it will end and the program will terminate.

Looking at the output, we see that this program will print a right triangle of asterisks that is x lines tall and x characters wide at the bottom.

A full execution of this program is shown in this animation.

Tutor 8 Animated Tutor 8 Animated

As we can see, working with for loops in Python is a quick and easy way to repeat a block of statements a specific number of times using the range() function.

Converting to a While Loop

It is possible to convert any for loop using the range() function in Python to a while loop. This can be done following a simple three-step pattern:

  1. Set an initial value for the iterator variable before the loop
  2. Update the iterator value at the end of each loop iteration
  3. Convert to a while loop and check the ending value of the iterator variable in the Boolean expression

Let’s look at the main() function from the example above:

def main():
    x = int(input("Enter a number: "))
    line = ""
    for i in range(x):
        line = line + "*"
        print(line)

To convert this for loop into a while loop, we can follow the three steps listed above. First, we must set an initial value for our iterator variable i before the loop. Since the first value stored in i is $0$, we’ll set it to that value.

def main():
    x = int(input("Enter a number: "))
    line = ""
    i = 0
    for i in range(x):
        line = line + "*"
        print(line)

Next, we need to update the value of the iterator variable at the end of each loop iteration. Since we didn’t provide an argument for the step value in the range() function, we know that i will just be incremented by $1$ each time. So, we’ll add a short line to increment i at the bottom of the loop:

def main():
    x = int(input("Enter a number: "))
    line = ""
    i = 0
    for i in range(x):
        line = line + "*"
        print(line)
        i = i + 1

Finally, we can switch the for loop to a while loop. In the Boolean expression, we want to repeat the loop while the iterator variable i has not reached the maximum value. So, we’ll use the Boolean expression i < x in the new while loop:

def main():
    x = int(input("Enter a number: "))
    line = ""
    i = 0
    while i < x:
        line = line + "*"
        print(line)
        i = i + 1

There we go! That’s the basic process for converting any for loop using range() in Python to be a while loop. It’s a useful pattern to know and recognize when it is used in code.

Subsections of For Loops

Input with Loops

YouTube Video

Resources

Loops in Python are also a great way to handle situations where a user must input a value that meets certain criteria. Previously, we used an if statement to determine if the input was valid, but if it wasn’t valid all we could do was print an error and end the program. If we use a loop instead, we can prompt the user to provide additional input until we receive a valid value.

To make this process easy to use, we can develop a function just to handle input from the user. For example, if we want the user to input a percentage, we could use a function similar to this one:

def input_percentage():
    x = float(input("Enter a percentage as a decimal number from 0 to 1: "))
    while(x < 0 or x > 1):
        print("Invalid Input!")
        x = float(input("Enter a percentage as a decimal number from 0 to 1: "))
    return x

This function starts by prompting the user to input a number, and stores it as a floating-point value in the variable x. Then, it will reach the start of the while loop. The Boolean expression will check to see if the value provided is not a percentage, so if it is less than $0$ or greater than $1$ it is considered invalid.

If the input is invalid, we’ll print an error and prompt the user for input again. We’ll keep repeating this process until the user provides a value between $0$ and $1$, at which point the loop will terminate. The last line in the function will return that value back to where it was called from.

Reading Input

With a function such as that in our program, we can use it in another function such as the main() function to actually read input directly from the user. For example, we can write a quick program to calculate a weighted average of exam scores as shown below:

def input_percentage():
    x = float(input("Enter a percentage as a decimal number from 0 to 1: "))
    while(x < 0 or x > 1):
        print("Invalid Input!")
1.         x = float(input("Enter a percentage as a decimal number from 0 to 1: "))
    return x


def main():
    print("This program will compute weighted average for three exam scores")
    print("Enter the first exam's score")
    exam1_score = input_percentage()
    print("Enter the first exam's weight")
    exam1_weight = input_percentage()
    print("Enter the second exam's score")
    exam2_score = input_percentage()
    print("Enter the second exam's weight")
    exam2_weight = input_percentage()
    print("Enter the third exam's score")
    exam3_score = input_percentage()
    print("Enter the third exam's weight")
    exam3_weight = input_percentage()
    total = exam1_score * exam1_weight + exam2_score * exam2_weight + exam3_score * exam3_weight
    print("Your total score is {}".format(total))


main()

A quick execution of this program is shown here:

Execution of Program Execution of Program

Take a minute to confirm that the program works by running it yourself, either in Python Tutor or directly in Python. You can even adapt this program to help calculate your final grade in this course!

Learning to how to create building blocks of larger programs, such as the input_percentage() function described here, is a great way to develop our programming skills. If we can learn to take a large program and break it down into smaller, repeatable chunks, then the entire program becomes much easier to develop and maintain.

Subsections of Input with Loops

Testing Loops

YouTube Video

Resources

Another important aspect of working with loops in code is learning how to properly test programs that contain loops. So, let’s look at some of the methods we can use when testing our programs containing loops to make sure they work correctly and won’t run into any problems.

Branch and Path Coverage

Loops, like conditional statements, introduce different branches and paths into our programs. So, one of the first steps when testing these programs is to develop a set of inputs that will execute each branch of the code, as well as finding inputs that will execute multiple different paths through the program. Since loops may repeat many times, typically we only worry about two possible paths when dealing with loops - one path where it is skipped entirely and another path where the loop is entered at least once.

Let’s consider this simple Python program that contains loops:

def main():
    x = int(input("Enter a number: "))
    y = int(input("Enter a number: "))
    i = 0
    line = ""
    while i < x + y:
        line = line + "*"
        print(line)
        i = i + 1
    while i > x - y:
        print("=", end="")
        i = i - 1
    print()


main()

This program will simply print a right triangle of asterisks * that is x + y characters wide and tall, similar to a previous example in this lab. However, it will also print a final line of equals signs = below that, consisting of x - y characters. To test this program, we want to try and find some input values for x and y that achieve branch and path coverage.

Branch Coverage

To achieve branch coverage, we want to execute each line of code in this program at least once. An easy way to do that is to provide inputs so that the sum of x and y is a positive number in order to enter the first loop. After the first loop terminates, the value of i will be equal to the sum of x and y, provided that value is greater than $0$. So, in order to also enter the second loop, we need to make sure the difference of x and y is smaller than their sum.

A set of inputs that achieves this would be $3$ and $1$ for x and y, respectively. Their sum is $4$, so the first loop will be executed 4 times. Then, the difference between the values is $2$, so the second loop will executed twice to reduce the value in i to $2$.

So, we can say that the inputs 3 and 1 are enough to achieve branch coverage.

Path Coverage

To achieve path coverage, we must consider three additional paths through the program:

  1. Neither loop is entered
  2. Only the first loop is entered
  3. Only the second loop is entered

First, in order to enter neither loop, we must have a set of inputs where the sum is less than or equal to $0$, and also where the difference is less than or equal to $0$. The simplest way to achieve this is to set both inputs to exactly $0$! Sometimes the simplest inputs are the easiest for testing.

To enter the first loop only, we need the situation where the sum of x and y is greater than 0, but then their difference is even larger. An easy way to accomplish this is to set y to a small negative number. For example, we could set x to $3$ and y to $-1$. That will make their sum $2$ but the difference $4$, so we’ll only execute the first loop but not the second one.

Finally, if we want to execute the second loop only, we need to make both the sum and the difference of x and y a number less than $0$. In that case, we can simply set both x and y to negative values, but make sure that x is overall a smaller value than y. So, we could set x to $-3$ and y to $-1$. In that case, their sum is $-4$ but their difference is $-2$. That will cause the program to skip the first loop but enter the second loop.

So, we can achieve path coverage with these four sets of inputs:

  • 3, 1
  • 0, 0
  • 3, -1
  • -3, -1

Loop Termination

When testing loops, we should also check to make sure that the loops will eventually terminate if we ever enter them. Thankfully, in this program, it is very easy to reason about this and convince ourselves that it works.

Consider the first loop - the only way to enter that loop is if the value of i is less than the sum of x and y, and each time the loop iterates the value in i will be incremented by $1$. Since i is getting larger each time, eventually it will reach a value that is greater than or equal to x + y and the loop will terminate.

The same argument can be made for the second loop. In that loop, i is initially greater than the difference of x and y, but i is decremented each time, so eventually the loop will terminate.

Technically speaking, we can come up with a loop variant for each loop and show that it is monotonically decreasing. This is easily done by simply finding the difference between the iterator variable i and the termination condition of the loop, either the sum or the difference of x and y. We won’t ask you to get to this level of detail when testing your own loops, but it is a very useful concept to understand when working with loops.

There we go! We were able to quickly come up with a set of inputs that will execute all possible branches and paths in this program, and we can also show that each of the loops will properly terminate anytime they are executed. So, we know that our program at least won’t crash, no matter what integers are provided as input. We can also examine the output of each test and make sure that it matches what the expected output should be in order to show that our program not only doesn’t crash, but that it provides the correct output.

Subsections of Testing Loops

Worked Example

YouTube Video

Resources

Now that we’ve learned all about using loops in Python, let’s go through a complete worked example to see how all the pieces fit together. As we continue to develop larger and more complex programs, it is helpful to observe the entire process from start to finish so we can see how to easily develop a program one step at a time. Once we’ve created our program, we’ll also perform a bit of testing to make sure that it works correctly.

Problem Statement

For this example, let’s write a program to play a simple number guessing game. Here’s how it works:

The game will select two random numbers from $0$ to $100$, one for each player. Then, each player will guess a number in the range, and the game will print either “higher” if that player’s secret number is larger than the guess, or “lower” if the player’s secret number is smaller than the guess. Players will alternate turns until one player correctly guesses their secret number and wins the game.

This game is pretty simple, but there are still quite a few different parts that we need to build in order to write the entire program. So, let’s go through the process of building a complete program in Python to play this game.

Random Numbers

Before we start building our program, we need some way to generate a random number. Thankfully, Python has a built-in library called Random that has many functions for working with random numbers in our programs.

To use a library in Python, we must first import it into our code. This is done using a simple import statement at the very top of our program’s code, followed by the name of the library we want to use. So, for this library, we’ll need to have the following import statement at the top of our source code:

import random

Once we’ve done that, we can use the function random.randint(a, b) to generate a random number that is between a and b, inclusive. Mathematically, we can say that it generates an integer $x$ such that $a <= x <= b$.

So, here’s a quick test program we can use to explore how to use random numbers in Python:

import random


def main():
    a = int(input("Enter a minimum value: "))
    b = int(input("Enter a maximum value: "))
    x = random.randint(a, b)
    print("A random number between {} and {} is {}".format(a, b, x))


main()

Take a minute to run this program and modify it a bit to make sure you understand how to work with random numbers in Python before continuing.

Basic Structure

In Python, we know that all of our programs must include a main() function and a call to the main() function, so we can start building our solution by creating that basic structure as shown here:

def main()


main()

In the program itself, we need to start by generating two random numbers from $0$ to $100$, one for each player. So, we can create and store those numbers in the main() function, and we’ll also need to import the random library at the top of our file.

import random


def main():
    p1_secret = random.randint(0, 100)
    p2_secret = random.randint(0, 100)


main()

Once we have the secret numbers, then we can sketch out the game itself. We’ll use a while loop to repeat until a player has guessed correctly, and we’ll also use a simple integer variable to keep track of the current player. Inside of the loop, we should get a new guess from a player, and also switch between players. So, let’s add that to our program structure using some comments:

import random


def main():
    p1_secret = random.randint(0, 100)
    p2_secret = random.randint(0, 100)
    player = 1
    while( ): # player has not guessed correctly
        # swap player
        # get new guess from player
        # check if secret is higher or lower


main()

From here, we can start to work on the actual logic in our program.

Boolean Expression

The trickiest part of this program is coming up with the correct Boolean expression to use in our while loop. That helps us determine if the player has guessed correctly and that the loop should stop. However, this is a bit complex since there are two different players, each one with their own secret number. So, in our Boolean expressions, we have to determine what the current player is as well as whether the most recent guess was correct.

To do this, we can use the and and or Boolean operators to combine several Boolean expressions together. For example, we can determine if the current player is player 1 using player == 1, and then we can determine if the most recent guess is equal to player 1’s secret number using guess == p1_secret. If we want both of those to be True in order to exit the loop, we can combine them using the and operator. We can do something very similar for player 2 as well. Then, if we want the loop to terminate if either of those is true, we can combine the two statements using the or operator.

So, all together, we want to repeat the loop as long as that entire statement is not True. So, the full Boolean expression would be:

not ((player == 1 and guess == p1_secret) or (player == 2 and guess == p2_secret))

That’s pretty complex, but it easily encompasses all of the rules of the game!

Handling Input

To get input from the user, we can write a simple read_input() function that is very similar to the ones we saw earlier in this lab. We simply want to make sure the user has provided a value that is between $0$ and $100$, and prompt the user for another input if not. We’ll also add in a parameter to print the current player in the prompt for input.

So, our read_input() function might look something like this:

def read_input(player):
    x = int(input("Enter a guess for player {}: ".format(player)))
    while x < 0 or x > 100:
        print("Invalid Input!")
        x = int(input("Enter a guess for player {}: ".format(player)))
    return x

Then, in our main() function, we can simply use the new read_input() function to read a player’s current guess as shown here:

def main():
    p1_secret = random.randint(0, 100)
    p2_secret = random.randint(0, 100)
    player = 1
    while(not ((player == 1 and guess == p1_secret) or (player == 2 and guess == p2_secret))): # player has not guessed correctly
        # swap player
        guess = read_input(player)
        # check if secret is higher or lower


main()

Checking Guess and Swapping Players

Next, we need to add some conditional statements to check if the secret is higher or lower than the guess, and also to swap between players. First, let’s look at swapping players. This is pretty simple, since all we need to do is find out what the current player is, and switch that value to the other player. So, we can do that using a simple if statement as shown here:

def main():
    p1_secret = random.randint(0, 100)
    p2_secret = random.randint(0, 100)
    player = 1
    while(not ((player == 1 and guess == p1_secret) or (player == 2 and guess == p2_secret))): # player has not guessed correctly
        if player == 1:
            player = 2
        else:
            player = 1
        guess = read_input(player)
        # check if secret is higher or lower


main()

The process for checking a guess is very similar - we can simply check to see if the guess is higher or lower than the secret for the current player, and print the output accordingly. First, we’ll need to figure out the current player:

if player == 1:
    # check player 1's secret
else:
    # check player 2's secret

Then, in each of those branches, we’ll check the guess:

if player == 1:
    if guess < p1_secret:
        print("Higher")
    elif guess > p1_secret:
        print("Lower")
    else:
        print("Correct!")
else:
    if guess < p2_secret:
        print("Higher")
    elif guess > p2_secret:
        print("Lower")
    else:
        print("Correct!")

This is a pretty straightforward set of conditional statements, but it goes to show how many lines of code are required even for a simple guessing-game program like this one.

Initial Testing and Debugging

So, at this point, we can put all of the pieces together and test our program. Here is the complete program that we’ve written so far:

import random


def read_input(player):
    x = int(input("Enter a guess for player {}: ".format(player)))
    while x < 0 or x > 100:
        print("Invalid Input!")
        x = int(input("Enter a guess for player {}: ".format(player)))
    return x


def main():
    p1_secret = random.randint(0, 100)
    p2_secret = random.randint(0, 100)
    player = 1
    while(not ((player == 1 and guess == p1_secret) or (player == 2 and guess == p2_secret))):
        if player == 1:
            player = 2
        else:
            player = 1
        guess = read_input(player)
        if player == 1:
            if guess < p1_secret:
                print("Higher")
            elif guess > p1_secret:
                print("Lower")
            else:
                print("Correct!")
        else:
            if guess < p2_secret:
                print("Higher")
            elif guess > p2_secret:
                print("Lower")
            else:
                print("Correct!")


main()

So, let’s try to run this program and see if it works. Unfortunately, right from the start we have an error in our code, since when we try to execute this program, we’ll get the following error:

Error 1 Error 1

Take a minute to see if you can spot and fix the error before continuing!

Unfortunately, in our Boolean expression inside of the while loop, we’re referencing the guess variable, but that variable isn’t actually created until later inside the while loop itself. So, we’ll need to set the guess variable to a value outside of the while loop first.

Also, we must be very careful about what value we use. If we set guess to be $0$ initially, there is a very small chance that player 1 could win the game immediately if their secret number is $0$, since that is a valid guess. Instead, let’s set guess to be $-1$ so that it will never be equal to a secret number.

After we make that change, when we run the program again, we may notice one other error:

Error 2 Error 2

Can you spot it by looking at this screenshot?

Unfortunately, our program starts with player 2’s turn instead of player 1. Why is that? Well, if we look closely at our code, we see that we are initially setting the player to player 1, but then immediately switching players inside of the while loop before we prompt for input. If we think about it, we can’t move the code that swaps players to later in the while loop, because then our Boolean expression to determine if the while loop should terminate will be incorrect. So, instead, we can simply initialize the player variable to be $2$, and then it will be switched to $1$ before we prompt for input the first time.

So, our final working program’s code with these two changes is shown here:

import random


def read_input(player):
    x = int(input("Enter a guess for player {}: ".format(player)))
    while x < 0 or x > 100:
        print("Invalid Input!")
        x = int(input("Enter a guess for player {}: ".format(player)))
    return x


def main():
    p1_secret = random.randint(0, 100)
    p2_secret = random.randint(0, 100)
    player = 2
    guess = -1
    while(not ((player == 1 and guess == p1_secret) or (player == 2 and guess == p2_secret))):
        if player == 1:
            player = 2
        else:
            player = 1
        guess = read_input(player)
        if player == 1:
            if guess < p1_secret:
                print("Higher")
            elif guess > p1_secret:
                print("Lower")
            else:
                print("Correct!")
        else:
            if guess < p2_secret:
                print("Higher")
            elif guess > p2_secret:
                print("Lower")
            else:
                print("Correct!")


main()

A full example of running this program, complete with a very lucky guess, is shown in the screenshot below:

Correct 1 Correct 1

Testing - Branch and Path Coverage

Now that we have a complete and working program, let’s look at what it might take to test it. Unfortunately, testing a program that uses random numbers can be very tricky, since each time you run the program you might receive different outputs even if you provide the same inputs.

Thankfully, we can still perform some testing to achieve branch and path coverage by choosing our guesses carefully. Here are the basic methods we’ll want to try:

  • Guessing $0$ for each player should result in "Higher" for output, unless the secret number is $0$.
  • Guessing $100$ for each player should result in "Lower" for output, unless the secret number is $100$.
  • Guessing the same value for one player while actually playing the game for the other player will eventually result in an output of "Correct". Using a bit of logic, it is always possible to win the game in no more than $7$ guesses.
  • Trying to input an invalid value should result in "Invalid Input!" for output at least once.

Following those simple strategies and keeping track of the various outputs received, it is pretty easy to eventually test all branches and most, if not all, possible paths through the program.

Testing - Loop Termination

Checking for loop termination on this program is a bit trickier, since the secret numbers are random and the guesses that a player makes are also unpredictable. So, while we can’t say for sure that the program will terminate, we can always prove that there is a possible condition where the loop will terminate, and that condition depends entirely on the user providing the correct input. So, as long as it is possible, we can say that the loop is at least not an infinite loop, and the program will work correctly if the user makes the correct guess.

There we go! We’ve just explored a complete example of writing a program in Python, complete with loops and conditional statements. We also introduced random numbers and how we can easily use those in our programs. Beyond that, we explored how to test these programs and correct minor errors found in testing. Hopefully this process is very helpful as we continue to work on larger and more complex programs.

Subsections of Worked Example

Summary

In this lab, we introduced several major important topics in Python. Let’s quickly review them.

Python While Loops

While loops in Python will execute while a Boolean expression evaluates to true.

while <boolean expression>:
    <block of statements>

Range Function

The range() function in Python is used to generate a list of numbers. It can be used in three ways:

  • range(stop) - numbers from $0$ up to (but not including) stop
  • range(start, stop) - numbers from start up to (but not including) stop
  • range(start, stop, step) - numbers from start up to (but not including) stop, with step between each number.

Python For Loops

For loops in Python will execute a set number of times.

for <iterator variable> in <list>:
    <block of statements>

Input with Loops

Loops can be used to request new input from the user if invalid input is received.

def input_percentage():
    x = float(input("Enter a percentage as a decimal number from 0 to 1: "))
    while(x < 0 or x > 1):
        print("Invalid Input!")
        x = float(input("Enter a percentage as a decimal number from 0 to 1: "))
    return x

Testing Loops

Loops can be tested for both branch and path coverage. In general, achieving path coverage involves writing code that will enter the loop, and also code that will bypass the loop entirely.

Loops should also be tested for termination and situations that may result in infinite loops. Using a loop variant and showing that it is monotonically decreasing is a helpful technique.