Iteration

Motivation

Many tasks are repetitive:

  • To print from 1 up to a user-specified number arbitrarily large.

  • To compute the maximum of a sequence of numbers arbitrarily long.

  • To get user input repeatedly until it is within a certain 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 400
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 what you need to do to change some 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.

For Loop

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. The expression list can also be an iterable object instead.

tuples = (0, "l"), (1, "o"), (2, "o"), (3, "p")
for i, c in tuples:
    print(i, c)
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

Iterate over a range

How to print up to a user-specified number?

We can use range:

%%mytutor -h 300
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? Try doing 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.

%%mytutor -h 300
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?
Try using a nested for loop: Write a for loop (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

Iterate over a string

What does the following do?

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

str is a sequence type because a string is 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'. Try using the for loop and indexing operator.

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

While Loop

How to ensure user input is non-empty?

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

%%mytutor -h 300
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 inputs nothing,

  • 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, c.f.,

    for i in range(5): print(i)
    
  • but it can be as efficient.

Should we just use while loop?

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

%%mytutor -h 310
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?

%%mytutor -h 300
for i in range(int(input(">")) + 1):
    print(i)

When the input corresponds to an integer \(\leq -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?)

Break/Continue/Else Constructs of a Loop

Breaking out of a loop

Is the following an infinite loop?

%%mytutor -h 310
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 use the assignment expression but it is not supported by Python version <3.8.

  • 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.

Continue to Next Iteration

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

%%mytutor -h 310
while True:
    message = input("Input something please:")
    if not message:
        continue
    print("You entered:", message)
  • The program repeatedly asks 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

Else construct for a loop

The following program checks whether a number is composite, namely,

  • a positive integer that is

  • a product of two strictly smaller positive integers.

@interact(num="1")
def check_composite(num):
    if num.isdigit():
        num = int(num)
        for divisor in range(2, num): # why starts from 2 instead of 1
            if num % divisor:
                continue  # where will this go?
            else:
                print("It is composite.")
                break  # where will this go?
        else:
            print("It is not composite.")  # how to get here?
    else:
        print("Not a positive integer.")  # how to get here?

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.

If program flow is confusing, try stepping through executation:

%%mytutor -h 520
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 Convert the for loop to a while loop. Try to make the code as efficient as possible with less computation and storage.

@interact(num="1")
def check_composite(num):
    if num.isdigit():
        num = int(num)
        ### BEGIN SOLUTION
        # for divisor in range(2,num):
        divisor = int(num ** 0.5)  # biggest possible divisor
        while divisor > 1:
            if num % divisor:
                divisor -= 1       # why not go from 2 to int(num ** 0.5)?
            else:
                print("It is composite.")
                break
        else:
            print("It is not composite.")
        ### END SOLUTION
    else:
        print("Not a positive integer.")