Loops and recursion
Loops and recursion
As a reminder, here is the insertion sort pseudocode:
index = 1 while index < length of list: insertion-index = index while list[insertion-index] < list[insertion-index - 1]: swap list[insertion-index] and list[insertion-index - 1] insertion-index = insertion-index - 1 index = index - 1
And here is our Python implementation:
def insertion_sort(l: list): """sorts the input list using the insertion sort algorithm""" index = 1 while index < len(l): insertion_index = index while insertion_index > 0 and l[insertion_index] < l[insertion_index - 1]: # swap the two elements element = l[insertion_index] l[insertion_index] = l[insertion_index - 1] l[insertion_index - 1] = element insertion_index = insertion_index - 1 index = index + 1
The body of the while loop corresponds to the “put the element in the right
place” operation we talked about when we were writing pseudocode for insertion
sort. What if we wanted to write it as a function, instead? We could call it
move_to_correct_location
. What arguments should it take? What should its body
be?
We might end up with something like this:
def move_to_correct_location(l: list, index: int): """Moves the element at index back in lst until it is at the correct location""" insertion_index = index while insertion_index > 0 and l[insertion_index] < l[insertion_index - 1]: # swap the two elements element = l[insertion_index] l[insertion_index] = l[insertion_index - 1] l[insertion_index - 1] = element insertion_index = insertion_index - 1
We can get rid of insertion_index
, since it’s now redundant with index
:
def move_to_correct_location(l: list, index: int): """Moves the element at index back in lst until it is at the correct location""" while index > 0 and l[index] < l[index - 1]: # swap the two elements element = l[index] l[index] = l[index - 1] l[index - 1] = element index = index - 1
There’s another way to write this function. In CSCI 0111, you saw two ways of writing functions that operated over lists: for-loops in Python and recursive functions–functions that call themselves–in Pyret. We can rewrite this function to use recursion instead of the while-loop. How might we do that?
Let’s start by getting rid of the while
loop. We can do this by turning it
into an if
. We can also get rid of the statement that increases
index
by 1:
def move_to_correct_location(l: list, index: int): """Moves the element at index back in lst until it is at the correct location""" if index > 0 and l[index] < l[index - 1]: # swap the two elements element = l[index] l[index] = l[index - 1] l[index - 1] = element
Let’s look at how this (incorrect) function executes on a small example like
[8, 9, 7]
. When it needs to move the 7
back (i.e., it’s called with 2
as
the index), what does it do?
First, we check to see if our index (2) is greater than zero, and that the
corresponding element (7) is less than the one at the previous index (8). It is,
so we swap the 7 and the 8. Now the list looks like [8, 7, 9]
. How can we
finish moving the 7 back? Do we have some function that will take the element at
index 1 and swap it with the previous element if necessary?
We do–it’s the function we’re writing! We can call move_to_correct_location
recursively:
def move_to_correct_location(l: list, index: int): """Moves the element at index back in l until it is at the correct location""" if index > 0 and l[index] >= l[index - 1]: element = l[index] l[index] = l[index - 1] l[index - 1] = element move_to_correct_location(l, index - 1)
We’ve now written a recursive version of our move_to_correct_location
function. Something to notice here: we’re not modifying the index
variable the
way we did in the while
case. So how are we moving the element back through
the list? The changing argument in calls to move_correct_location
plays the
role of the index
variable!
Recurring on numbers
Let’s talk a bit more about what’s happening here. In Pyret, we saw lots of
recursive functions on data structures like lists (and trees!). When we wrote
recursive functions on lists, we always had two cases: one for the empty
list, and one for a link
(some element linked to the rest of the list). Our
recursive function here looks a little different: we’re passing in the same list
each time. The second argument to the function, though, is a number (an array
index, specifically), and it changes with every call. One way to think about it
is that just as a list can be either:
- the empty list or
- a link,
an array index can be either:
0
1 + n
, wheren
is another index
How does this work in our move_to_correct_location
function? In the zero case,
we immediately return–we don’t need to do any work. In the 1+n
case, we move
the element back from 1+n
if necessary, then call ourselves on the index n
.
More fun with while loops and recursion
Factorial
The factorial function comes up frequently in probability and statistics (among
other disciplines). The factorial of n
(written n!
is the product of the
first n
integers; for instance,
3! = 3 * 2 * 1 = 6
How would we implement factorial using loops? How about recursion?
def factorial_loop(n: int) -> int: total = 1 while n > 1: total = total * n n = n - 1 return total def factorial_rec(n: int) -> int: if n <= 1: return 1 return n * factorial_rec(n - 1)