cs116 - module 5 - types of recursion

22
CS116 - Module 5 - Types of Recursion Cameron Morland Winter 2020 1 CS116 - Module 5 - Types of Recursion

Upload: others

Post on 13-Feb-2022

3 views

Category:

Documents


0 download

TRANSCRIPT

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