More on Loops¶
Overview, Objectives, and Key Terms¶
In Lecture 8, the use of while
loops in
Python introduced to solve problems requiring iteration. The while
loop structure in Python is very similar to structure introduced via
pseudocode in Lecture_5. In this lecture,
an additional for
-loop construct is introduced, which reduces the
amount of “bookkeeping” required in some cases. All loops can also be
nested, which provides substantial flexibility when processing data,
defining multidimensional arrays, and performing similar, potentially
multidimensional tasks.
Objectives¶
By the end of this lesson, you should be able to
- Use a
for
loop to solve simple problems using iteration - Use nested
for
loops to fill the elements of a multidimensional array. - Use the graphical debugger in Spyder to trace and debug a program with iteration
Key Terms¶
for
range
continue
- loop variable
- dependent loop variable
The for
Loop¶
Remember once again the problem of printing out each element of an array
a
. In Lecture 8, the following solution
was proposed using a while
loop:
# print out the elements of an array using a while loop
import numpy as np
a = np.array([1, 1, 2, 3, 5, 8, 13])
n = len(a)
i = 0
while i < n: # always remember the :
print(a[i]) # indented 4 spaces
i = i + 1 # also indented 4 spaces
The hallmark of such a while
loop is the counter (here, the i
).
Defining a counter is not hard, but it’s a pain when we forget to update
it (infinite loop), and there’s always a chance that the update is
wrong. An alternative construct in Python (and other languages) that
works for such “counter” problems is the for
loop. Let me introduce
this by example:
In [1]:
# print out the elements of an array using a for loop
import numpy as np
a = np.array([1, 1, 2, 3, 5, 8, 13])
for i in range(0, len(a)): # always remember the :
print(a[i]) # indented 4 spaces
1
1
2
3
5
8
13
Some of this is identical to the while
-loop solution: the same a
and the same print
. However, we don’t have the i = 0
initialization, nor is i
updated inside the while
loop. And, in
place of while i < n
, we have for i in range(0, len(a))
. That is
a very common pattern when using Python for
loops. Here’s what
range
does:
In [2]:
range(0, len(a))
Out[2]:
range(0, 7)
In [3]:
type(range(0, len(a)))
Out[3]:
range
In [4]:
np.array(range(0, len(a)))
Out[4]:
array([0, 1, 2, 3, 4, 5, 6])
In other words, range
is a built-in function and type (note the
syntax color), but it represents a sequence of integers as shown by the
conversion to an ndarray
. The range
function accepts three
arguments: start
, end
, and stride
, similar to slicing:
In [5]:
# just one argument means the end of the range
np.array(range(5))
Out[5]:
array([0, 1, 2, 3, 4])
In [6]:
# two arguments means the start and end
np.array(range(1,5))
Out[6]:
array([1, 2, 3, 4])
In [7]:
# three arguments means the start, end, and stride
np.array(range(0, 5, 2))
Out[7]:
array([0, 2, 4])
The for i in
structure is not limited to range
, though. In fact,
i
can come from any sequential type, like ndarray
or the
list
and tuple
types we’ll cover later on. Hence, we can also do
In [8]:
for i in np.arange(5):
print(i)
0
1
2
3
4
and
In [9]:
for x in np.linspace(0, 1, 6):
print("x = ", x)
x = 0.0
x = 0.2
x = 0.4
x = 0.6
x = 0.8
x = 1.0
A Pythonic Quirk¶
One reason that while
loops were introduced first is because a
for
loop in Python actually follows a slightly different logic than
an apparently identical while
loop. Consider these examples:
In [10]:
n = 5
i = 0
while i < n:
print("i = ", i)
i += 2
i = 0
i = 2
i = 4
In [11]:
# A "quirky" for loop
for j in range(n):
print ("j = ", j)
j += 2
j = 0
j = 1
j = 2
j = 3
j = 4
Something is amiss. Both loops appear to take the counter i
(or
j
) from 0 to 5 in jumps of 2. However, even though we set j
to 2
after just one iteration of the for
loop, the next time around,
j
appears to be 1. In other words, even if we modify the counter
variable within a for
loop, it will take on a predefined value at
the next iteration. These predefined values are those numbers in
range(n)
. They are defined once, and unless the for
loop is
terminated using a break
, the counter j
will be take on each
value in range(n)
. Of course, if we really want to have j
jump
by two each time, we could do
In [12]:
for j in range(0, n, 2):
print ("j = ", j)
j += 2
j = 0
j = 2
j = 4
In practice, this “quirk” of Python for
loops should not lead to
problems, but it is important to understand (especially for those who
plan to program in other languages like C++, for which changes made to
the counter inside a for
modify the loop execution).
Note. Changing the counter in a Python
for
loop does not modify the loop behavior.Exercise: Try to define a while loop (in pseudocode or Python, or as a flowchart) that exhibits the same behavior as the “quirky”
for
loop above.
Nested for
loops¶
Just like if
statements can be nested, so, too, can for
(and
while
) loops be nested. While nested if
statements can always be
written as (potentially much) more complicated, single statements, there
are some tasks for which nested loops are truly required—but I have
not proven that!
Exercise: Use a nested for
loop to find the sum of the elements
of \(5\times 5\) array of random numbers.
Solution:
In [13]:
n = 5
# The seed function just makes the random numbers the
# same every time so that these notes don't keep changing!
np.random.seed(1234)
# The rand function can produce single values or arrays.
# Note that its syntax for 2-D arrays does *not* use
# the double parentheses like np.ones and np.zeros
A = np.random.rand(n, n)
s = 0
for i in range(n):
for j in range(n):
s += A[i, j]
print('sum = ', s)
sum = 13.7180626162
Of course, we can use the built-in sum
function (useful here to
check our logic):
In [14]:
sum(sum(A))
Out[14]:
13.718062616246673
Exercise: Why do we need to use sum
twice?
For the exercise above, each loop used a variable (i
and j
) that
were independent. In other words, j
was not dependent on i
, and
i
did not dependend on j
. This represents the simplest case of
nested loops. Sometimes, though, an inner loop variable (here, j
)
depends on i
. For example, consider the problem of computing the
cumulative sum of an array \(a\). The cumulative sum of an array is
another array of the same length whose \(i\)th element is defined
as
For example, the cumulative sum of an array of three ones has the elements 1, 2, and 3.
Exercise: Use a for
loop to compute the cumulative sum of an
array of 5 random numbers.
Solution:
In [15]:
a = A[0] # steal the first row of the 2-D array above
c = 0*a # easy way to initialize array of same size
for i in range(n):
for j in range(i+1):
c[i] += a[j]
print(a)
print(c)
[ 0.19151945 0.62210877 0.43772774 0.78535858 0.77997581]
[ 0.19151945 0.81362822 1.25135596 2.03671454 2.81669035]
The last element of c
ought to be the sum of a
:
In [16]:
c[-1]==sum(a)
Out[16]:
True
Using loops effectively requires practice, and nested loops in particular deserve ample attention. Here are some additional exercises to tackle:
Exercise: Starting with A = np.zeros((5, 5))
, use two loops to
produce
array([[ 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20],
[21, 22, 23, 24, 25]])
Exercise: Starting with A = np.zeros((5, 5))
, use two loops to
produce
array([[ 1, 2, 3, 4, 5],
[ 0, 7, 8, 9, 10],
[ 0, 0, 13, 14, 15],
[ 0, 0, 0, 19, 20],
[ 0, 0, 0, 0, 25]])
Exercise: Starting with A = np.zeros((5, 5))
, use two loops to
produce
array([[ 1, 2, 3, 4, 5],
[ 0, 6, 7, 8, 9],
[ 0, 0, 10, 11, 12],
[ 0, 0, 0, 13, 14],
[ 0, 0, 0, 0, 15]])
Exercise: Use loops to compute the cumulative product of
a = np.array([2,4,7,3,9])
.
One Last Tidbit: continue
¶
There are some occasions where, once something is done within a loop, you want to move right to the next iteration. For example, suppose we want to sum and print all the even integers from 1 through \(n\). An obvious solution is
In [17]:
n = 10
s = 0
for i in range(1, n+1):
if not i % 2:
s += i
print(i)
print('s = ', s)
2
4
6
8
10
s = 30
However, the continue
statement let’s us rewrite the program
slightly as
In [18]:
n = 10
s = 0
for i in range(1, n+1):
if i % 2:
continue
s += i
print(i)
print('s = ', s)
2
4
6
8
10
s = 30
In some cases, use of continue
may be simpler than if
statements
alone. Personally, I rarely use them. Note that continue
(and, for
that matter, break
) applies only to the nearest for
or while
containing it. For example:
In [19]:
for i in range(5):
print('i=', i)
for j in range(5):
if (j + i) % 2:
continue
elif i == 2:
break
print(' j=', j)
i= 0
j= 0
j= 2
j= 4
i= 1
j= 1
j= 3
i= 2
i= 3
j= 1
j= 3
i= 4
j= 0
j= 2
j= 4
Exercise: Load the previous example in Spyder and trace it using the
graphical debugger. Try to guess whether or not the j
will be
printed for any possible values of i
and j
.
Further Reading¶
None at this time.