Section 1: Designing with loops
Table of Contents >
Chapter 3 > Section 1
The template for a function that consumes a
(listof X)
and
that processes the list using a loop rather than natural recursion is as
follows:
def fn_for_lox(lox):
acc = ...
for x in lox:
...x
acc = ...x ...acc
return ...acc
The construct
for x in lox:
should be read "for each
x
in
lox
do something". The particular something that we want
to do is specified by the indented block of code following the
colon. In this case, we can optionally do something with
x
and we will want to update the value of
acc
by doing
something with
x
and the current value of
acc
.
The variable
acc
is used to accumulate whatever value is
needed as we process the loop. Note that in some cases, more than
one accumulator may be needed.
If you have already designed a function using tail recursion and an
accumulator, redesigning the code using a loop is relatively
straightforward. We illustrate this process for our
sum_loi
function in
Screencast
3.1.
The final version of this code is presented in
Code
Explorer 3.1.
So now let's see what happens when we try to sum the entries in a list
that contains a large amount of data:
>>> sum_loi_l(list(range(10)))
45
>>> sum_loi_l(list(range(100)))
4950
>>> sum_loi_l(list(range(1000)))
499500
>>> sum_loi_l(list(range(1000000)))
499999500000
As we are no longer making recursive calls to our function, we are not
limited by the depth of recursion imposed by the Python interpreter and
can process data that is much larger.
Note that sometimes we will design a function using natural
recursion. We may then find that the size of the data causes the
maximum depth of recursion to be exceeded, in which case we re-factor the
design to use a loop. In other cases, we may start by designing the
function using a loop.
Example
Suppose we want to design a function that consumes a list of integers and
a threshold value, and determines how many integers in the list are below
the threshold. If we are to design a solution using a loop, we must
determine what we need to accumulate as the list is processed. In
this case, we need to keep track of how many of the integers processed so
far are below the given threshold value.
It is often helpful to walk through an example so that you can see how the
necessary values are accumulated as the list is processed. Let's
assume that we want to determine how many entries in the list
[4,
-3, 0, 6, 8]
are below the threshold value of
4
.
value to be processed
|
count_below : how many below
threshold
so far
|
-
|
0
|
4
|
0
|
-3
|
1
|
0
|
2
|
6
|
2
|
8
|
2
|
Recall that the Python for-each loop will successively provide us with the
next item to be processed in the list. We need to determine what it
is that we wish to accumulate as we process the list. In this case,
we want to accumulate the number of items in the list that are below the
threshold value, so we'll use an accumulator
count_below
initialized with the value 0 for this purpose. Then,
for
each value in the list, we determine if that value is below the
threshold value of 4. If it is, we add one to the accumulator
count_below
,
otherwise we simply move on to the next item in the list. The final
version of this code is presented in
Code
Explorer 3.2.