Comp 112
Lecture 9
Recursive Functions2018.03.27
In its body, a function may call other functions.
One of those functions may be itself.
A function that calls itself in the process of computing its result is called a recursive function.
There is a conditional analyzing the argument(s) to decide whether the function should call itself.
A branch where the function doesn’t call itself is a base case.
A self-call of the function is a recursive call.
Many programming problems can be solved by using either iteration or recursion:
iteration solves problems by building up solutions using loops and accumulators or effects.
recursion solves problems by breaking them down into similar but smaller problems with recursive function calls.
iteration makes progress as loop variables get “closer” to failing the guard condition.
recursion makes progress as the arguments to recursive calls get “closer” to a base case.
iteration requires the programmer to structure subproblem solution composition. (“manual bookkeeping”)
recursion uses the language’s function-calling mechanism to build up solutions from subproblem solutions. (“automatic bookkeeping”)
iteration tackles problems from a “global” perspective.
recursion tackles problems for a “local” perspective.
To solve a problem by recursion:
Identify the cases for which you can solve the problem directly. These are the base cases.
For any remaining cases, work out how you could use the solution to a smaller problem (e.g. smaller number, shorter string or list, etc.) in order to solve the current problem.
In each case, write out how to solve the problem for just the current case, without worrying about how to solve other cases or recursive subproblems.
This is enough to solve any problem so long as you:
cover all possible cases,
do the right thing in each case,
ensure that the recursive calls eventually reach a base case.
A common strategy for writing recursive functions for lists:
A list is either empty ([]
), or else it’s not. This often provides the case distinction for recursive functions on lists.
An empty list has no smaller slices, so it must be a base case.
A nonempty list must have at least one element, so there is always an element at index 0
. This element is called the list head:
A nonempty list also has a slice beginning at index 1
and ending at the end of the list. This slice is called the list tail:
The list tail is a list that is one element shorter than the original list. In this sense it’s “smaller”.
Often, a recursive call is made on the list tail.
This strategy also works for strings.
A palindrome is a word that reads the same forward as backward.
We can characterize the property of being a palindrome with the following specification:
The empty string is a palindrome.
A non-empty string is a palindrome if the first and last characters are equal and the rest of the characters, excluding the first and last, themselves form a palindrome.
This specification is recursive because in the second case we refer to the same property that we are describing.
It’s easy to turn this recursive specification into a recursive predicate function.
A self-similar shape is one whose description is recursive, i.e. it contains a reference to itself.
Very simple recursive descriptions can produce interesting self-similar shapes
def tree (size , depth) :
# signature: int , int -> NoneType
# precondition: size >= 0 and depth >= 0
if depth == 0 :
pass
elif depth > 0 :
turtle.forward (size)
turtle.left (30.0)
tree (4/5 * size , depth - 1)
turtle.right (60.0)
tree (4/5 * size , depth - 1)
turtle.left (30.0)
turtle.back (size)
Here’s a helper-function for speeding up deeply-nested recursive drawings:
def faster (drawer , *args) :
delay = turtle.delay ()
frames = turtle.tracer()
turtle.tracer (300 , 100)
drawer (*args)
turtle.update ()
turtle.tracer (frames , delay)
e.g.
Starting with an equilateral triangle
we can draw a scaled-down version of it in each of its three corners
and scaled down version of those in each of their corners
and so on and so on…
The result is called a Sierpinski triangle:
def sierpinski_triangle (size , depth) :
# signature: int , int -> NoneType
# precondition: size >= 0 and depth >= 0
if depth == 0 :
pass
elif depth > 0 :
for i in range (0 , 3) : # for each of the three sides of a triangle:
sierpinski_triangle (size / 2 , depth - 1) # draw a scaled-down fractal triangle
turtle.forward (size) # draw an edge of the current triangle
turtle.left (120.0) # turn to create a corner of the current triangle
Once we have figured out its recursive structure, we can optimize it by pruning the empty branch:
Reading:
Come to lab on Thursday.
Complete homework 9.