Lecture 8: While Loops

Objectives for today

while loops

We previously used for loops to execute a block of code for a specific number of repetitions. What if we don’t know how many iterations are needed? What might be such a situation? Obtaining a valid input from a user. We don’t know how many tries it will take someone to provide a valid input.

This is where we apply while loops.

General structure:

while <bool expression>:
    statement1
    statement2
    ...
    statementn

The statements in the loop body (i.e., statement1 … statementn) will be executed repeatedly until the boolean expression, the loop conditional, evaluates to False.

Here is a concrete example. What will this code print?

>>> x = 5
>>> while x > 0:
...    print(x)
...    x = x-1
... 
5
4
3
2
1

What is the implication of while loops? That some statement(s) within the body of the while loop will change the loop conditional (or otherwise terminate the loop). What happens if that is not the case, e.g.,

while True:
    print("How many times will this string get printed?")

Hit Ctrl-C (Ctrl and C simultaneously) to stop execution.

This is called an infinite loop and is a common problem. You will likely need to use Ctrl-C at some point.

What about the following loop? Will it terminate?

i = 0
while i < 10:
    print("How many times will this string get printed?")
    i = i - 1

No. Because i starts at 0 and only gets smaller it will always be less than 10. Consider the following loop – will it terminate?

i = randint(1, 20)
while i < 10:
    print("How many times will this string get printed?")
    i = i - 1

It depends. If the value of i is less than 10, the loop will run infinitely. If the value is greater than or equal to 10, the loop won’t execute at all.

In addition to changing the loop conditional we can also explicitly terminate the loop with the break statement. As its name suggests, break terminates the loop and begins executing the first statement after the loop.

Peer instruction questions (While loops) [1] (Section A, Section B)


Random Guessing Game

Let’s look at a more complex example: Let’s implement a guessing game in which Python picks a random integer from 1-20 (inclusive), and keeps asking the user to guess the number until they get it right. To help the user, the game should give hints “higher”, or “lower”.

What would be some key elements in such a program?

Check out guessing-game.py.

Here we see 3 strategies for implementing the loop:

  1. Use a boolean variable correct to track if the user answered correctly
  2. Compare the user response to the desired answer in the loop conditional
  3. break out of a “while True” loop on a correct answer

As an aside, the last simulates the “do-while loop” semantics available in other languages.

Note the use of the input function for reading user inputs:

>>> help(input)
Help on built-in function input in module builtins:

input(prompt=None, /)
    Read a string from standard input.  The trailing newline is stripped.
    
    The prompt string, if given, is printed to standard output without a
    trailing newline before reading input.
    
    If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError.
    On *nix systems, readline is used if available.

input returns a string and so we need to convert the result to an int for use in our game.

While vs For loops

Often we can implement the same functionality with both a for loop and a while loop. In fact, in Python any for loop can be readily implemented with a while loop. The reverse is trickier and not always possible, so for our purposes we should think of while loops as a superset of for loops.

So when do we use a for loop and when do we use a while loop?

We use a for loop when

As an example, iterating over all the elements in a sequence, e.g., a string, is an example of a situation where the number of iterations is known at the initiation of the loop (number of elements in sequence) and the increment is consistent (increment one element each iteration).

We would use a while loop in other settings, such as

This an example where “style” matters but there are not necessarily clear “rules”. Often one approach or the other is more appropriate. The right choice will make the code more elegant, easier to reason about (and easier to debug).

Non-boolean types that can be used as booleans

Will the following print statement get executed?

if "a string?":
    print("Do I get executed?")

Yes. Most values can be used in a boolean context. In Python, 0, 0.0, None, empty sequences (e.g., “”) and a few other values evaluate as False and everything else is True.

In general, using this “implicit” type conversion is not recommended as it just increases the chances for difficult-to-find bugs.

Now that we know about implicit booleans, though, we can resolve a common bug. In the Socrative question, the solution was a == b or a == 5. Can we simplify that expression as a == b or 5? No, that expression is evaluated as (a == b) or 5, which is not the same (and will always evaluate to True as 5 evaluates as True).

Short circuit evaluation

Let’s think about a and b. If a evaluates to False do I need to evaluate b? Similarly if in a or b a evaluates to True do I need to evaluate b?

No.

Python can “short-circuit” the evaluation. Doing so is a very powerful technique for managing potentially problematic situations.

is_valid(input) and dangerous_operation(input)

Comparing string and other non-numeric types

We can also apply relational operators, i.e., <, ==, etc., to other types; most notably strings. For example:

>>> 'Aardvark' < 'Zebra'
True
>>> 'aardvark' < 'Zebra'
False

What is happening in these two comparisons? For strings, a comparison operator such as < implements lexicographic ordering, i.e., it compares the ordering of corresponding characters. The first characters are compared, if equal, then the 2nd characters are compared and so on. If one string is a substring of the other, it is lexicographically less than, e.g.,

>>> "abc" < "abcdef"
True

This is not the same as a case-insensitive alphabetical ordering. In the character encoding used by Python (and lots of other software), all upper case letters are less than lower case letters. Hence “aardvark” is “greater than” “Zebra”.

Some more examples:

>>> "test" == "test"
True
>> "test" == "testing"[0:4]
True
>> "test" == "TEST"
False

If we wanted to ensure that a string comparison was case insensitive, how could be we do so? Use the upper or lower methods to ensure consistent case.

Summary

  1. while loops
  2. Boolean details
  3. Quiz 4 and Lab 4 on Friday. Complete the in-class exercises and Prelab 4 beforehand.
  1. Guessing game code
  2. ASCII Wikipedia or a more compact ASCII table