Upload
others
View
3
Download
0
Embed Size (px)
Citation preview
CS116 - Module 5 - Types of Recursion
Cameron Morland
Winter 2020
1 CS116 - Module 5 - Types of Recursion
Types of Recursion
Some programmers classify recursion as either Structural or Non-Structural (“generative”).
Some separately classify it as Accumulative or Non-Accumulative.
Structural,Non-Accumulative
Non-Structural,Non-Accumulative
Structural,Accumulative
Non-Structural,Accumulative
So far we have used mostly structural, non-accumulative recursion.3 CS116 - Module 5 - Types of Recursion
Structural Recursion – what we’ve been doing so far
Templates for code are based on recursive definitions of the data. Examples:
A Nat is either:
1 0
2 or n + 1 where n is a Nat.
def factorial(n):if n == 0:return 1
else:return n * factorial(n-1)
Racket lists were defined recursively.A (listof Int) is either
1 '()
2 or (cons v L) where v is a Int and L is a(listof Int).
This is not how it works in Python, but we canstill process a list L that way, using L[0] insteadof first and L[1:] instead of rest.def sum(L):if n == []:return 0
else:return L[0] + sum(L[1:])
These both match the countdown template we used in Racket.
Non-Structural (“generative”) Recursion
Non-Structural (“generative”) Recursion is Recursion not modelled after the structure of thedata.
For example,def is_palindrome(s):if len(s) < 2:return True
else:return s[0] == s[-1] and is_palindrome(s[1:-1])
This does not match the structure of s so it is not structural recursion.
5 CS116 - Module 5 - Types of Recursion
Non-Structural (“generative”) Recursion
Consider new ways (other than the definition of the data) to break into subproblems.
Requires more creativity in solutions.
We can do more –
Different solutions techniques.Even more problems can be solved.
More variations – no standard template.
Non-Structural (“generative”) Recursion is the alternative to structural recursion.
We don’t really care about the difference between Structural and Non-Structural (“generative”)Recursion. It merely means that you cannot rely on templates to design your code.
6 CS116 - Module 5 - Types of Recursion
Trace factorial(5)
def factorial(n):' ' ' factorial: Nat -> Nat ' ' 'if n == 0:return 1
else:return n * factorial(n-1)
factorial(5)
=> 5 * factorial(4)
=> 5 * (4 * factorial(3))
=> 5 * (4 * (3 * factorial(2)))
=> 5 * (4 * (3 * (2 * factorial(1))))
=> 5 * (4 * (3 * (2 * (1 * factorial(0)))))
=> 5 * (4 * (3 * (2 * (1 * 1))))
=> 5 * (4 * (3 * (2 * 1)))
=> 5 * (4 * (3 * 2))
=> 5 * (4 * 6)
=> 5 * 24
=> 120
The computer keeps track of all variables to multiply later, and this takes memory.
8 CS116 - Module 5 - Types of Recursion
Accumulative Recursion
Instead of leaving all the multiplication to the end, “accumulate” it as we go. The recursivefunction will consume the accumulated values.We’ll need a helper function to initialize the accumulator.
9 CS116 - Module 5 - Types of Recursion
Accumulative factorial
def remember_fact(product, n0):' ' ' remember_fact: Nat Nat -> Nat ' ' 'if n0 <= 0:return product
else:return remember_fact(product*n0, n0-1)
def accumulative_factorial(n):' ' ' accumulative_factorial: Nat -> Nat ' ' 'return remember_fact(1, n)
accumulative_factorial(5)
=> remember_fact(1, 5)
=> remember_fact(5, 4)
=> remember_fact(20, 3)
=> remember_fact(60, 2)
=> remember_fact(120, 1)
=> remember_fact(120, 0)
=> 120
There is nothing left over to multiply later, so memory usage may not increase.
10 CS116 - Module 5 - Types of Recursion
Differences and similarities in implementations
remember_fact needs a helper function to keep track of the work done so far
Both implementations are correct, but
factorial does all calculations after reaching the base caseremember_fact does the calculations as we go.product is called the “accumulator”
Mathematically equivalent, but not computationally equivalent.
11 CS116 - Module 5 - Types of Recursion
Accumulative Recursion
This technique is called accumulative recursion.
It may be used with structural recursion, in which case it may be called structuralrecursion with an accumulator.
It may also be used with non-structural (“generative”) recursion.
12 CS116 - Module 5 - Types of Recursion
Accumulative Recursion
Wrapper Function:
The wrapper function sets the initial valueof the accumulator(s).
The wrapper function may also handlespecial cases.
Recall:def accumulative_factorial(n):return remember_fact(1, n)
Helper Function:
Does most of the work.
Requires at least two parameters:
The accumulator records what has beendone so far.The remainder records what remains tobe done (and is used to identify the basecases).
Some problems may need more than oneaccumulator or tracker.
Recall:def remember_fact(product, n0):...
13 CS116 - Module 5 - Types of Recursion
Accumulative function pattern
def acc_helper(acc, remaining , ...):if at base case of remaining: return accelse:
## ... maybe do some extra work here ...
return acc_helper(updated acc, updated remaining , ...)## Key point: the return has *nothing* outside the acc_helper call.
## We return *exactly* what acc_helper returns.
def fn(...):## Consider special cases, as needed.
return ... acc_helper(initial acc, initial remaining , ...) ...
Exe
rcise
Rewrite this non-accumulative function list_sum to create an accumulative functionlist_sum(L) which consumes a (listof Int) and returns the sum.def list_sum(L):
if L == []:return 0
else:return L[0] + list_sum(L[1:])
Accumulative Fibonacci Numbers
The nth Fibonacci number is the sum of the two previous Fibonacci numbers:
f0 = 0, f1 = 1, fn = fn−1 + fn−2, n ≥ 2
Exe
rcise
Type in this program, and verify that it works.def fib(n):
' ' ' Returns the nth Fibonacci number.fib: Nat -> Nat ' ' 'if n == 0: return 0elif n == 1: return 1else: return fib(n-1) + fib(n-2)
What is fib(30) ?What is fib(35) ?What is fib(40) ?
This is a purely structural program, but it is very slow.
Exe
rcise
Write fib(n) that uses accumulative recursion to compute the first n Fibonacci numbers.Hint: use a helper extend_fib(fibs, n) where fibs accumulates the result.
Reversing a List
This is non-accumulative recursion:def invert(L):
' ' ' Return a list with the elements of L in reverse orderinvert: (listof Any) -> (listof Any) ' ' 'if len(L) <= 1:
return Lelse:
return invert(L[1:]) + [L[0]]# ˆˆˆˆˆˆˆˆˆ
# Stuff outside the recursive call
# means it ' s non-accumulative.
Exe
rcise
def invert(orig):mylist = orig.copy()
acc = []
acc_invert(mylist, acc)
return acc
Write an accumulative helper functionacc_invert(L, A) which will allow this to return aninverted copy of orig. Use append and pop so at theend L == [] and A contains the answer.
Avoiding the recursion depth limit
Recall the function range: list(range(5)) => [0,1,2,3,4]Using the structural recursive my_max, we are limited in the length of the list we can work on.my_max(list(range(5))) => 4, but my_max(list(range(2000))) results inRecursionError: maximum recursion depth exceeded in comparison
Why? Each recursive call shortens the list by only one. We make another recursive call foreach item in the list.
Let’s break the list up a different way: non-structural (“generative”) recursion.Instead of making one recursive call on L[1:] and comparing that with L[0], split the list in twomore or less equal halves, and recurse on each of those.
L[:len(L)//2] => first half L[len(L)//2:] => second half
Exe
rcise
By splitting the list in two halves, use non-structural (“generative”) recursion to write afunction that returns the maximum value in a (listof Int).
20 CS116 - Module 5 - Types of Recursion
Steps for Non-Structural (“generative”) Recursion
1 Break the problem into any subproblem(s) that seem natural for the problem.
2 Determine the base case(s).
3 Solve the subproblems, recursively if necessary.
4 Determine how to combine subproblem solutions to solve the original problem.
5 Test! Without a template, it can be more difficult to be sure you have tested allpossibilities. Test thoroughly.
21 CS116 - Module 5 - Types of Recursion
Example: gcd
The greatest common divisor (gcd) oftwo natural numbers is the largestnatural number that divides evenlyinto both.
gcd(10, 25) = 5
gcd(20, 22) = 2
gcd(47, 21) = 1
This is a structurally recursive function thatcomputes gcd. It uses the countdown template on n.def gcd_countdown(a, b, n):
' ' ' Return the greatest divisorof a and b which is <= n.
gcd: Nat Nat -> Nat ' ' 'if a % n == 0 and b % n == 0:
return nelse:
return gcd_countdown(a, b, n-1)
def gcd(a, b):' ' ' Return the gcd of a and b.gcd: Nat Nat -> Nat ' ' 'return gcd_countdown(a, b, a)
22 CS116 - Module 5 - Types of Recursion
Euclid’s Algorith for gcd
def gcd(a, b):if a == 0: return belif b == 0: return aelse: return gcd(b, a % b)
This is not structural; it is generative. But it
still has a base case
still is recursive
23 CS116 - Module 5 - Types of Recursion
Tracing accumulative, non-structural (“generative”) recursionEx. Given mylist = [1,0,4,3,2], trace find(mylist, 0).
def help(L, i, s):' ' ' ...ouch...help: (listof Nat) Nat Nat -> Nat ' ' 'if L[i] == s:
return ielse:
return help(L, L[i], s)
def find(L,i):' ' ' Find where i is in L.find: (listof Nat) Nat -> Nat ' ' 'return help(L, i, i)
Exe
rcise
Now with mylist = [4,2,0,4,3], trace find(mylist, 0).What happens and why?
Locally defined functions (inner functions) in Python
You can’t have two variables with the same name, even if one is a function:
def foo(x):' ' ' Returns 1 more than x.foo: Int -> Int ' ' 'return x + 1
def bar(y):' ' ' Returns 2*(y+1)bar: Int -> Int ' ' 'return foo(2*y)
bar(5) => 11
foo = 42
bar(5)
-> TypeError: 'int' object is not callable
...but you can define functions locally:def bar(y):
' ' ' Returns 2*(y+1).bar: Int -> Int ' ' '
def foo(x):' ' ' Returns 1 more than x.foo: Int -> Int ' ' 'return x + 1
return foo(2*y)
bar(5) => 11
foo = 42
bar(5) => 11
You may now define helper functions locally on assignments. You still need the design recipe.
Locally defined functions and design recipes for accumulative recursion
You need to write the design recipe even for locally defined functions.As in Racket, you cannot test locally defined functions. If possible, it’s a good idea to writethem as ordinary functions and test them before moving them inside.def fib4(n):
' ' ' Returns nth Fibonacci number.fib4: Nat -> Nat
Example: fib4(10) => 55 ' ' 'def acc (n0, last, prev):
' ' ' Returns (n+n0)th Fibonacci number,where last is the (n)th, and prev is (n-1)th
acc: Nat Nat Nat -> Nat ' ' 'if n0 >= n: return lastelse:
return acc(n0+1, last+prev, last)
# Body of fib4
if n==0: return 0else: return acc(1,1,0)
26 CS116 - Module 5 - Types of Recursion
Goals of Module 5
Understand how to write recursive functions in Python.
Write functions that are structurally recursive, and ones that are not structurally recursive.
Write functions that use accumulative recursion.
Before we begin the next module, please
Read Think Python, chapter 7.
28 CS116 - Module 5 - Types of Recursion