5. Iteration

5.1. Motivation

Many tasks are repetitive:

  • To print from 1 up to a user-specified number, which can be arbitrarily large.

  • To compute the maximum of a sequence of numbers, which can be arbitrarily long.

  • To repeatedly ask users for input until the input is within the right range.

How to write code to perform repetitive tasks?

E.g., can you complete the following code to print from 1 up to a user-specified number?

%%mytutor -h 300
num = int(input('>'))
if 1 < num: print(1)
if 2 < num: print(2)
if 3 < num: print(3)
# YOUR CODE HERE 

code duplication is not good because:

  • Duplicate code is hard to read/write/maintain.
    Imagine there is a small change needed to every duplicate code.

  • The number of repetitions may not be known before runtime.

Instead, programmers write a loop which specifies a piece of code to be executed iteratively.

5.2. For Loop

5.2.1. Iterate over a sequence

How to print from 1 up to 4?

We can use a for statement as follows:

%%mytutor -h 300
for i in 1, 2, 3, 4:
    print(i)
  • i is automatically assigned to each element in the sequence 1, 2, 3, 4 one-by-one from left to right.

  • After each assignment, the body print(i) is executed.

N.b., if i is defined before the for loop, its value will be overwritten.

The assignment is not restricted to integers and can also be a tuple assignment.

tuples = (0,'l'), (1,'o'), (2,'o'), (3,'p')
for i,c in tuples: print(i,c)  # one-liner
0 l
1 o
2 o
3 p

An even shorter code…

for i,c in enumerate('loop'): print(i,c)
0 l
1 o
2 o
3 p

5.2.2. Iterate over a range

How to print up to a user-specified number?

We can use range:

stop = int(input('>')) + 1
for i in range(stop):
    print(i)

Why add 1 to the user input number?

range(stop) generates a sequence of integers from 0 up to but excluding stop.

How to start from a number different from 0?

for i in range(1,5): print(i)
1
2
3
4

What about a step size different from 1?

for i in range(0,5,2): print(i)  # starting number must also be specified. Why?
0
2
4

Exercise How to count down from 4 to 0? Do it without addition or subtraction.

### BEGIN SOLUTION
for i in range(4,-1,-1): print(i)
### END SOLUTION
4
3
2
1
0

Exercise Print from 0 to a user-specified number but in steps of 0.5.
E.g., if the user inputs 2, the program should print:

0.0
0.5
1.0
1.5
2.0

Note: range only accepts integer arguments.

num = int(input('>'))
### BEGIN SOLUTION
for i in range(0, 2 * num + 1, 1):
    print(i / 2)
### END SOLUTION

Exercise How to print the character '*' repeatedly for m rows and n columns?
Hint: Use a nested for loop, i.e., write a for loop (called inner loop) inside the body of another for loop (outer loop).

@interact(m=(0, 10), n=(0, 10))
def draw_rectangle(m=5, n=5):
    ### BEGIN SOLUTION
    for i in range(m):
        for j in range(n):
            print('*', end='')
        print()
    ### END SOLUTION

5.2.3. Iterate over a string

What does the following do?

%%mytutor -h 300
for character in 'loop': print(character)

A string is iterable because it can be regarded as a sequence of characters.

  • The function len can return the length of a string.

  • The indexing operator [] can return the character of a string at a specified location.

message = "loop"
print('length:', len(message))
print('characters:', message[0], message[1], message[2], message[3])
length: 4
characters: l o o p

We can also iterate over a string as follows although it is less elegant:

for i in range(len('loop')): print('loop'[i])
l
o
o
p

Exercise Print a string assigned to message in reverse.
E.g., 'loop' should be printed as 'pool'.

@interact(message='loop')
def reverse_print(message):
    ### BEGIN SOLUTION
    for i in range(len(message)):
        print(message[-i - 1], end='')
    ### END SOLUTION

5.3. While Loop

How to repeatedly ask the user to enter an input until the user input is not empty?

Python provides the while statement to loop until a specified condition is false.

while not input('Input something please:'): pass

As long as the condition after while is true, the body gets executed repeatedly. In the above example,

  • if user press enter without inputting anything,

  • input returns an empty string '', which is regarded as False, and so

  • the looping condition not input('...') is True.

Is it possible to use a for loop instead of a while loop?

  • Not without hacks because the for loop is a definite loop which has a definite number of iterations before the execution of the loop.

  • while statement is useful for an indefinite loop where the number of iterations is unknown before the execution of the loop.

It is possible, however, to replace a for loop by a while loop.
E.g., the following code prints from 0 to 4 using a while loop instead of a for loop.

i = 0
while i <= 4:
    print(i)
    i += 1
0
1
2
3
4
  • A while loop may not be as elegant (short), c.f., for i in range(5): print(i), but

  • it can always be as efficient.

Should we just use while loop?

Consider using the following while loop to print from 0 to a user-specified value.

num = int(input('>'))
i = 0
while i!=num+1: 
    print(i)
    i += 1

Exercise Is the above while loop doing the same thing as the for loop below?

for i in range(int(input('>')) + 1): print(i)

When user input negative integers smaller than or equal to -2,

  • the while loop becomes an infinite loop, but

  • the for loop terminates without printing any number.

We have to be careful not to create unintended infinite loops.
The computer can’t always detect whether there is an infinite loop. (Why not?)

5.4. Break/Continue/Else Constructs of a Loop

5.4.1. Breaking out of a loop

Is the following an infinite loop?

%%mytutor -h 300
while True:
    message = input('Input something please:')
    if message: break
print('You entered:', message)

The loop is terminated by the break statement when user input is non-empty.

Why is the break statement useful?

Recall the earlier while loop:

%%mytutor -h 300
while not input('Input something please:'): pass 

This while loop is not useful because it does not store the user input.

Is the break statement strictly necessary?

We can avoid break statement by using flags, which are boolean variables for flow control:

%%mytutor -h 350
has_no_input = True
while has_no_input:
    message = input('Input something please:')
    if message: has_no_input = False
print('You entered:', message)

Using flags makes the program more readable, and we can use multiple flags for more complicated behavior.
The variable names for flags are often is_..., has_..., etc.

5.4.2. Continue to Next Iteration

What does the following program do?
Is it an infinite loop?

%%mytutor -h 300
while True:
    message = input('Input something please:')
    if not message: continue
    print('You entered:', message)
  • The program repeatedly ask the user for input.

  • If the input is empty, the continue statement will skip to the next iteration.

  • The loop can only be terminated by interrupting the kernel.

  • Such an infinite loop can be useful. E.g., your computer clock continuously updates the current time.

Exercise Is the continue statement strictly necessary? Can you rewrite the above program without the continue statement?

%%mytutor -h 350
while True:
    message = input('Input something please:')
    ### BEGIN SOLUTION
    if message:
        print('You entered:', message)
    ### END SOLUTION

5.4.3. Else construct for a loop

The following program

  • checks whether the user input is a positive integer using isdigit, and if so,

  • check if the positive integer is a composite number, i.e., a product of two smaller positive integers.

@interact(num='1')
def check_composite(num):
    if num.isdigit():
        num = int(num)
        for divisor in range(2,num):
            if num % divisor:
                continue
            else:
                print('It is composite.')
                break
        else:
            print('It is not composite.')
    else:
        print('Not a positive integer.')
%%mytutor -h 500 
def check_composite(num):
    if num.isdigit():
        num = int(num)
        for divisor in range(2,num):
            if num % divisor:
                continue
            else:
                print('It is composite.')
                break
        else:
            print('It is not composite.')
    else:
        print('Not a positive integer.')
        
check_composite('1')
check_composite('2')
check_composite('3')
check_composite('4')

In addition to using continue and break in an elegant way,
the code also uses an else clause that is executed only when the loop terminates normally not by break.

Exercise There are three else claues in the earlier code. Which one is for the loop?

  • The second else clause that print('It is not composite.').

  • The clause is called when there is no divisor found in the range from 2 to num.

Exercise Convert the for loop to a while loop.
Can you improve the code to use fewer iterations?

@interact(num='1')
def check_composite(num):
    if num.isdigit():
        num = int(num)
        # for divisor in range(2,num):    # use while instead
        divisor = 2
        while divisor <= num**0.5: 
            if num % divisor:
                divisor += 1
            else:
                print('It is composite.')
                break
        else:
            print('It is not composite.')
    else:
        print('Not a positive integer.')