$$ \newcommand{\tp}{\thinspace .} $$

This chapter is taken from the book A Primer on Scientific Programming with Python by H. P. Langtangen, 5th edition, Springer, 2016.

Alternative implementations with lists and loops

We have already solved the problem of printing out a nice-looking conversion table for Celsius and Fahrenheit degrees. Nevertheless, there are usually many alternative ways to write a program that solves a specific problem. The next paragraphs explore some other possible Python constructs and programs to store numbers in lists and print out tables. The various code snippets are collected in the program file session.py.

While loop implementation of a for loop

Any for loop can be implemented as a while loop. The general code

for element in somelist:
    <process element>
can be transformed to this while loop:

index = 0
while index < len(somelist):
    element = somelist[index]
    <process element>
    index += 1
In particular, the example involving the printout of a table of Celsius and Fahrenheit degrees can be implemented as follows in terms of a while loop:

Cdegrees = [-20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40]
index = 0
print '    C    F'
while index < len(Cdegrees):
    C = Cdegrees[index]
    F = (9.0/5)*C + 32
    print '%5d %5.1f' % (C, F)
    index += 1

The range construction

It is tedious to write the many elements in the Cdegrees in the previous programs. We should use a loop to automate the construction of the Cdegrees list. The range construction is particularly useful in this regard:

A for loop over integers are written as

for i in range(start, stop, step):
    ...
We can use this construction to create a Cdegrees list of the values \( -20, -15, \ldots, 40 \):

Cdegrees = []
for C in range(-20, 45, 5):
    Cdegrees.append(C)

# or just
Cdegrees = range(-20, 45, 5)
Note that the upper limit must be greater than 40 to ensure that 40 is included in the range of integers.

Suppose we want to create Cdegrees as \( -10, -7.5, -5, \ldots, 40 \). This time we cannot use range directly, because range can only create integers and we have decimal degrees such as \( -7.5 \) and \( 1.5 \). In this more general case, we introduce an integer counter \( i \) and generate the \( C \) values by the formula \( C=-10 + i\cdot 2.5 \) for \( i=0,1,\ldots, 20 \). The following Python code implements this task:

Cdegrees = []
for i in range(0, 21):
     C = -10 + i*2.5
     Cdegrees.append(C)

For loops with list indices

Instead of iterating over a list directly with the construction

for element in somelist:
    ...
we can equivalently iterate of the list indices and index the list inside the loop:

for i in range(len(somelist)):
    element = somelist[i]
    ...
Since len(somelist) returns the length of somelist and the largest legal index is len(somelist)-1, because indices always start at 0, range(len(somelist)) will generate all the correct indices: 0, 1, \( \ldots \), len(somelist)-1.

Programmers coming from other languages, such as Fortran, C, C++, Java, and C#, are very much used to for loops with integer counters and usually tend to use for i in range(len(somelist)) and work with somelist[i] inside the loop. This might be necessary or convenient, but if possible, Python programmers are encouraged to use for element in somelist, which is more elegant to read.

Iterating over loop indices is useful when we need to process two lists simultaneously. As an example, we first create two Cdegrees and Fdegrees lists, and then we make a list to write out a table with Cdegrees and Fdegrees as the two columns of the table. Iterating over a loop index is convenient in the final list:

Cdegrees = []
n = 21
C_min = -10
C_max = 40
dC = (C_max - C_min)/float(n-1)  # increment in C
for i in range(0, n):
     C = -10 + i*dC
     Cdegrees.append(C)

Fdegrees = []
for C in Cdegrees:
    F = (9.0/5)*C + 32
    Fdegrees.append(F)

for i in range(len(Cdegrees)):
    C = Cdegrees[i]
    F = Fdegrees[i]
    print '%5.1f %5.1f' % (C, F)

Instead of appending new elements to the lists, we can start with lists of the right size, containing zeros, and then index the lists to fill in the right values. Creating a list of length n consisting of zeros (for instance) is done by

somelist = [0]*n
With this construction, the program above can use for loops over indices everywhere:

n = 21
C_min = -10
C_max = 40
dC = (C_max - C_min)/float(n-1)  # increment in C

Cdegrees = [0]*n
for i in range(len(Cdegrees)):
     Cdegrees[i] = -10 + i*dC

Fdegrees = [0]*n
for i in range(len(Cdegrees)):
    Fdegrees[i] = (9.0/5)*Cdegrees[i] + 32

for i in range(len(Cdegrees)):
    print '%5.1f %5.1f' % (Cdegrees[i], Fdegrees[i])
Note that we need the construction [0]*n to create a list of the right length, otherwise the index [i] will be illegal.

Changing list elements

We have two seemingly alternative ways to traverse a list, either a loop over elements or over indices. Suppose we want to change the Cdegrees list by adding 5 to all elements. We could try

for c in Cdegrees:
    c += 5
but this loop leaves Cdegrees unchanged, while

for i in range(len(Cdegrees)):
    Cdegrees[i] += 5
works as intended. What is wrong with the first loop? The problem is that c is an ordinary variable, which refers to a list element in the loop, but when we execute c += 5, we let c refer to a new float object (c+5). This object is never inserted in the list. The first two passes of the loop are equivalent to

c = Cdegrees[0]   # automatically done in the for statement
c += 5
c = Cdegrees[1]   # automatically done in the for statement
c += 5
The variable c can only be used to read list elements and never to change them. Only an assignment of the form

Cdegrees[i] = ...
can change a list element.

There is a way of traversing a list where we get both the index and an element in each pass of the loop:

for i, c in enumerate(Cdegrees):
    Cdegrees[i] = c + 5
This loop also adds 5 to all elements in the list.

List comprehension

Because running through a list and for each element creating a new element in another list is a frequently encountered task, Python has a special compact syntax for doing this, called list comprehension. The general syntax reads

newlist = [E(e) for e in list]
where E(e) represents an expression involving element e. Here are three examples:

Cdegrees = [-5 + i*0.5 for i in range(n)]
Fdegrees = [(9.0/5)*C + 32 for C in Cdegrees]
C_plus_5 = [C+5 for C in Cdegrees]
List comprehensions are recognized as a for loop inside square brackets and will be frequently exemplified throughout the document.

Traversing multiple lists simultaneously

We may use the Cdegrees and Fdegrees lists to make a table. To this end, we need to traverse both arrays. The for element in list construction is not suitable in this case, since it extracts elements from one list only. A solution is to use a for loop over the integer indices so that we can index both lists:

for i in range(len(Cdegrees)):
    print '%5d %5.1f' % (Cdegrees[i], Fdegrees[i])
It happens quite frequently that two or more lists need to be traversed simultaneously. As an alternative to the loop over indices, Python offers a special nice syntax that can be sketched as

for e1, e2, e3, ... in zip(list1, list2, list3, ...):
    # work with element e1 from list1, element e2 from list2,
    # element e3 from list3, etc.
The zip function turns \( n \) lists (list1, list2, list3, ...) into one list of \( n \)-tuples, where each \( n \)-tuple (e1,e2,e3,...) has its first element (e1) from the first list (list1), the second element (e2) from the second list (list2), and so forth. The loop stops when the end of the shortest list is reached. In our specific case of iterating over the two lists Cdegrees and Fdegrees, we can use the zip function:

for C, F in zip(Cdegrees, Fdegrees):
    print '%5d %5.1f' % (C, F)
It is considered more Pythonic to iterate over list elements, here C and F, rather than over list indices as in the for i in range(len(Cdegrees)) construction.