1 of 116

Recursion for Beginners:

A Beginner's Guide to Recursion

@AlSweigart al@inventwithpython.com

bit.ly/nbpython2018recursion

2 of 116

3 of 116

4 of 116

5 of 116

To understand recursion,

you must first understand recursion.

6 of 116

Recursion: n. blah blah blah See also: recursion

7 of 116

8 of 116

9 of 116

10 of 116

I.S.M.E.T.A.

11 of 116

12 of 116

13 of 116

A recursive thing is something whose definition includes itself.

14 of 116

Sierpinski Triangle

https://github.com/asweigart/recursion_examples

15 of 116

16 of 116

In programming, recursion is when a function calls itself.

17 of 116

In programming, recursion is when a function calls itself.

def shortest():

shortest()

18 of 116

Traceback (most recent call last):

File "shortest.py", line 4, in <module>

shortest()

File "shortest.py", line 2, in shortest

shortest()

File "shortest.py", line 2, in shortest

shortest()

File "shortest.py", line 2, in shortest

shortest()

[Previous line repeated 996 more times]

RecursionError: maximum recursion depth exceeded

19 of 116

Traceback (most recent call last):

File "shortest.py", line 4, in <module>

shortest()

File "shortest.py", line 2, in shortest

shortest()

File "shortest.py", line 2, in shortest

shortest()

File "shortest.py", line 2, in shortest

shortest()

[Previous line repeated 996 more times]

RecursionError: maximum recursion depth exceeded

20 of 116

To understand recursion,

you must first understand recursion.

21 of 116

To understand recursion,

you must first understand recursion.

STACKS

22 of 116

23 of 116

A stack is a data structure that holds a sequence of data and only lets you interact with the topmost item.

24 of 116

A stack is a data structure that holds a sequence of data and only lets you interact with the topmost item.

First-In, Last-Out (FILO)

25 of 116

A stack is a data structure that holds a sequence of data and only lets you interact with the topmost item.

26 of 116

Adding to the top of the stack is called pushing.

Removing from the top of the stack is called popping.

27 of 116

Adding to the top of the stack is called pushing.

Removing from the top of the stack is called popping.

Python’s lists are a stack if you only use append() and pop().

28 of 116

>>> spam = []

>>> spam.append('Alice')

>>> spam.append('Bob')

>>> spam.append('Carol')

>>> spam.pop()

'Carol'

>>> spam

['Alice', 'Bob']

29 of 116

def a():

print('Start of a()')

b()

print('End of a()')

def b():

print('Start of b()')

c()

print('End of b()')

def c():

print('Start of c()')

print('End of c()')

a()

30 of 116

def a():

print('Start of a()')

b()

print('End of a()')

def b():

print('Start of b()')

c()

print('End of b()')

def c():

print('Start of c()')

print('End of c()')

a()

Start of a()

Start of b()

Start of c()

End of c()

End of b()

End of a()

31 of 116

def a():

print('Start of a()')

b()

print('End of a()')

def b():

print('Start of b()')

c()

print('End of b()')

def c():

print('Start of c()')

print('End of c()')

a()

Start of a()

Start of b()

Start of c()

End of c()

End of b()

End of a()

“GOTO Considered Harmful”

32 of 116

The “call stack” is a stack of “frame objects”.

(frame object == a function call)

33 of 116

Recursion is a lot easier to understand if you understand stacks and the call stack.

34 of 116

def a():

print('Start of a()')

b()

print('End of a()')

def b():

print('Start of b()')

c()

print('End of b()')

def c():

print('Start of c()')

print('End of c()')

a()

Start of a()

Start of b()

Start of c()

End of c()

End of b()

End of a()

35 of 116

def a():

print('Start of a()')

b()

print('End of a()')

def b():

print('Start of b()')

c()

print('End of b()')

def c():

print('Start of c()')

print('End of c()')

a()

Start of a()

Start of b()

Start of c()

End of c()

End of b()

End of a()

“Where’s the call stack?”

36 of 116

Extra Credit

Look up the inspect and traceback modules.

Doug Hellmann’s “Python Module of the Week” blog:

  • https://pymotw.com/3/inspect/
  • https://pymotw.com/3/traceback/

37 of 116

38 of 116

Recursion is overrated.

39 of 116

40 of 116

Factorial

5! = 5 × 4 × 3 × 2 × 1 = 120

2! = 2 × 1 = 2

4! = 4 × 3 × 2 × 1 = 24

41 of 116

Factorial

5! = 5 × 4 × 3 × 2 × 1 = 120

4! = 4 × 3 × 2 × 1 = 24

42 of 116

Factorial

5! = 5 × 4 × 3 × 2 × 1 = 120

4! = 4 × 3 × 2 × 1 = 24

5! = 5 × 4!

43 of 116

Factorial

5! = 5 × 4 × 3 × 2 × 1 = 120

4! = 4 × 3 × 2 × 1 = 24

5! = 5 × 4! = 5 × 24 = 120

44 of 116

Factorial

Number! = Number × (Number - 1)!

(Factorial has a recursive nature.)

45 of 116

Factorial

Number! = Number × (Number - 1)!

def factorial(number):

return number * factorial(number - 1)

print(factorial(5))

46 of 116

Factorial

Traceback (most recent call last):

File "factorial.py", line 4, in <module>

print(factorial(5))

File "factorial.py", line 2, in factorial

return number * factorial(number - 1)

File "factorial.py", line 2, in factorial

return number * factorial(number - 1)

File "factorial.py", line 2, in factorial

return number * factorial(number - 1)

[Previous line repeated 996 more times]

RecursionError: maximum recursion depth exceeded

47 of 116

Factorial

5! = 5 × 4 × 3 × 2 × 1 = 120

48 of 116

Factorial

5! = 5 × 4 × 3 × 2 × 1 = 120

5! = 5 × 4 × 3 × 2 × 1 × 0 × -1 × -2 × ...

49 of 116

Factorial

5! = 5 × 4 × 3 × 2 × 1 = 120

5! = 5 × 4 × 3 × 2 × 1 × 0 × -1 × -2 × ...

A stack overflow is when a recursive function gets out of control and doesn’t stop recursing.

50 of 116

Factorial

Traceback (most recent call last):

File "factorial.py", line 4, in <module>

print(factorial(5))

File "factorial.py", line 2, in factorial

return number * factorial(number - 1)

File "factorial.py", line 2, in factorial

return number * factorial(number - 1)

File "factorial.py", line 2, in factorial

return number * factorial(number - 1)

[Previous line repeated 996 more times]

RecursionError: maximum recursion depth exceeded

51 of 116

Factorial

def factorial(number):

return number * factorial(number - 1)

print(factorial(5))

52 of 116

Factorial (hint: 1! = 1)

def factorial(number):

return number * factorial(number - 1)

print(factorial(5))

53 of 116

Factorial (hint: 1! = 1)

def factorial(number):

if number == 1:

return 1

return number * factorial(number - 1)

print(factorial(5))

54 of 116

Factorial (hint: 1! = 1)

def factorial(number):

if number == 1:

return 1 # BASE CASE

# RECURSIVE CASE

return number * factorial(number - 1)

print(factorial(5))

55 of 116

Your recursive function must always have at least one base case and one recursive case.

56 of 116

Factorial (hint: 1! = 1)

def factorial(number):

if number == 1:

return 1 # BASE CASE

# RECURSIVE CASE

return number * factorial(number - 1)

print(factorial(5))

57 of 116

Factorial (hint: 1! = 1)

def factorial(number):

if number == 1:

return 1 # BASE CASE

# RECURSIVE CASE

return number * factorial(number - 1)

print(factorial(5))

“Where does the 5 × 4 ×

3 × 2 × 1 happen?”

58 of 116

The “call stack” is a stack of “frame objects”.

(frame object == a function call)

59 of 116

The “call stack” is a stack of “frame objects”.

(frame object == a function call)

60 of 116

The “call stack” is a stack of “frame objects”.

(frame object == a function call)

61 of 116

Frame objects are where local variables are stored.

62 of 116

def factorial(number):

if number == 1:

return 1 # BASE CASE

# RECURSIVE CASE

return number * factorial(number - 1)

63 of 116

# Hard-coded pseudo-recursive algorithm

def factorial5():

return 5 * factorial4()

def factorial4():

return 4 * factorial3()

def factorial3():

return 3 * factorial2()

def factorial2():

return 2 * factorial1()

def factorial1():

return 1

print(factorial5())

64 of 116

# Iterative factorial algorithm

def factorial(number):

total = 1

for i in range(1, number):

total *= i

return total

print(factorial(5))

65 of 116

66 of 116

# Using iteration to emulate recursion.

callStack = [] # The explicit call stack, which holds "frame objects".

callStack.append({'instrPtr': 'start', 'number': 5}) # "Call" the "factorial() function"

returnValue = None

while len(callStack) > 0:

# The body of the "factorial() function":

number = callStack[-1]['number'] # Set number "parameter".

instrPtr = callStack[-1]['instrPtr']

if instrPtr == 'start':

if number == 1:

# BASE CASE

returnValue = 1

callStack.pop() # "Return" from "function call".

continue

else:

# RECURSIVE CASE

callStack[-1]['instrPtr'] = 'after recursive call'

# "Call" the "factorial() function":

callStack.append({'instrPtr': 'start', 'number': number - 1})

continue

elif instrPtr == 'after recursive call':

returnValue = number * returnValue

callStack.pop() # "Return from function call".

continue

print(returnValue)

67 of 116

68 of 116

When should we use recursion?

69 of 116

When should we use recursion?

  • Never.

70 of 116

When should we use recursion?

71 of 116

When should we use recursion?

  • When the problem has a tree-like structure.

72 of 116

When should we use recursion?

  • When the problem has a tree-like structure.
  • When the problem requires backtracking.

73 of 116

When should we use recursion?

  • When the problem has a tree-like structure.
  • When the problem requires backtracking.

(Both of these are required.)

74 of 116

75 of 116

76 of 116

77 of 116

78 of 116

79 of 116

80 of 116

81 of 116

So when should we use recursion?

  • When the problem has a tree-like structure.
  • When the problem requires backtracking.

Otherwise, you don’t have to use recursion.

82 of 116

factorial(1001)

83 of 116

factorial(1001)

Traceback (most recent call last):

File "factorialByRecursion.py", line 8, in <module>

print(factorial(1001))

File "factorialByRecursion.py", line 7, in factorial

return number * factorial(number - 1)

File "factorialByRecursion.py", line 7, in factorial

return number * factorial(number - 1)

File "factorialByRecursion.py", line 7, in factorial

return number * factorial(number - 1)

[Previous line repeated 995 more times]

File "factorialByRecursion.py", line 2, in factorial

if number == 1:

RecursionError: maximum recursion depth exceeded in comparison

84 of 116

Tail Call Optimization/Elimination

What if the problem is big enough that it really does require more than 1000 function calls?

factorial(1001)

85 of 116

Tail Call Optimization/Elimination

In code, tail call optimization/elimination is when the recursive function call is the last thing in the function before it returns:

def recursiveFunc(params):

# blah blah blah

return recursiveFunc(params) # RECURSIVE CASE

(The recursive function call comes at the “tail” of the function.)

86 of 116

Tail Call Optimization/Elimination

return recursiveFunc(params) # RECURSIVE CASE

You won’t need to hold on to local variables, because there’s no code after the recursive function call that will need them.

87 of 116

Tail Call Optimization/Elimination

return recursiveFunc(params) # RECURSIVE CASE

You won’t need to hold on to local variables, because there’s no code after the recursive function call that will need them.

There’s no need to keep the frame object on the call stack.

88 of 116

Tail Call Optimization/Elimination

return recursiveFunc(params) # RECURSIVE CASE

You won’t need to hold on to local variables, because there’s no code after the recursive function call that will need them.

There’s no need to keep the frame object on the call stack.

You can go beyond 1000 function calls because the call stack isn’t growing.

TCO prevents stack overflows.

89 of 116

Tail Call Optimization/Elimination

Tail call optimization is overrated.

90 of 116

Normal Recursive Factorial Can’t be TC Optimized

def factorial(number):

if number == 1:

# BASE CASE

return 1

else:

# RECURSIVE CASE

return number * factorial(number - 1)

print(factorial(5))

91 of 116

Factorial with Tail Call Optimization

def factorial(number, accumulator=1):

if number == 0:

# BASE CASE

return accumulator

else:

# RECURSIVE CASE

return factorial(number - 1, number * accumulator)

print(factorial(5))

92 of 116

Tail call optimization is a compiler trick...

93 of 116

Tail call optimization is a compiler trick…

...that CPython doesn’t implement...

94 of 116

Tail call optimization is a compiler trick…

...that CPython doesn’t implement...

...and never will.

95 of 116

“If you want a short answer, it's simply unpythonic.”

-Guido

http://neopythonic.blogspot.com.au/2009/04/tail-recursion-elimination.html

http://neopythonic.blogspot.com.au/2009/04/final-words-on-tail-calls.html

96 of 116

97 of 116

Fibonacci Sequence

1, 1, 2, 3, 5, 8, 13, 21, 34 ...

98 of 116

Fibonacci Sequence

1, 1, 2, 3, 5, 8, 13, 21, 34 …

fib(1) = 1, fib(2) = 1, fib(3) = 2, fib(4) = 3

99 of 116

Fibonacci Sequence

1, 1, 2, 3, 5, 8, 13, 21, 34 …

fib(1) = 1, fib(2) = 1, fib(3) = 2, fib(4) = 3

fib(N) = fib(N - 1) + fib(N - 2)

fib(1) and fib(2) = 1

100 of 116

Recursive Fibonacci

def fib(nthNumber):

if nthNumber == 1 or nthNumber == 2:

# BASE CASE

return 1

else:

# RECURSIVE CASE

return fib(nthNumber - 2) + fib(nthNumber - 1)

101 of 116

102 of 116

103 of 116

104 of 116

Recursive Fibonacci with Memoization

FIB_CACHE = {}

def fib(nthNumber):

if nthNumber in FIB_CACHE:

return FIB_CACHE[nthNumber]

if nthNumber == 1 or nthNumber == 2:

# BASE CASE

return 1

else:

# RECURSIVE CASE

FIB_CACHE[nthNumber] = fib(nthNumber - 2) + fib(nthNumber - 1)

return FIB_CACHE[nthNumber]

105 of 116

Recursive Fibonacci with Memoization

import functools

@functools.lru_cache()

def fib(nthNumber):

if nthNumber == 1 or nthNumber == 2:

# BASE CASE

return 1

else:

# RECURSIVE CASE

return fib(nthNumber - 2) + fib(nthNumber - 1)

106 of 116

107 of 116

  • To understand recursion, you must first understand stacks.

108 of 116

  • To understand recursion, you must first understand stacks.
  • Recursion is hard to learn because the call stack is invisible.

109 of 116

  • To understand recursion, you must first understand stacks.
  • Recursion is hard to learn because the call stack is invisible.
  • You always need at least one base case and one recursive case.

110 of 116

  • To understand recursion, you must first understand stacks.
  • Recursion is hard to learn because the call stack is invisible.
  • You always need at least one base case and one recursive case.
  • Function calls push a frame object onto the stack, returning pops them off.

111 of 116

  • To understand recursion, you must first understand stacks.
  • Recursion is hard to learn because the call stack is invisible.
  • You always need at least one base case and one recursive case.
  • Function calls push a frame object onto the stack, returning pops them off.
  • Stack overflows happen when you make too many function calls without returning.

112 of 116

  • To understand recursion, you must first understand stacks.
  • Recursion is hard to learn because the call stack is invisible.
  • You always need at least one base case and one recursive case.
  • Function calls push a frame object onto the stack, returning pops them off.
  • Stack overflows happen when you make too many function calls without returning.
  • Anything you can do with recursion you can do with a loop and a stack.

113 of 116

  • To understand recursion, you must first understand stacks.
  • Recursion is hard to learn because the call stack is invisible.
  • You always need at least one base case and one recursive case.
  • Function calls push a frame object onto the stack, returning pops them off.
  • Stack overflows happen when you make too many function calls without returning.
  • Anything you can do with recursion you can do with a loop and a stack.
  • Use recursion only when the problem has a tree-like structure and requires backtracking.

114 of 116

  • To understand recursion, you must first understand stacks.
  • Recursion is hard to learn because the call stack is invisible.
  • You always need at least one base case and one recursive case.
  • Function calls push a frame object onto the stack, returning pops them off.
  • Stack overflows happen when you make too many function calls without returning.
  • Anything you can do with recursion you can do with a loop and a stack.
  • Use recursion only when the problem has a tree-like structure and requires backtracking.
  • Tail call elimination prevents stack overflows by preventing the call stack from growing. CPython doesn’t implement TCO.

115 of 116

  • To understand recursion, you must first understand stacks.
  • Recursion is hard to learn because the call stack is invisible.
  • You always need at least one base case and one recursive case.
  • Function calls push a frame object onto the stack, returning pops them off.
  • Stack overflows happen when you make too many function calls without returning.
  • Anything you can do with recursion you can do with a loop and a stack.
  • Use recursion only when the problem has a tree-like structure and requires backtracking.
  • Tail call elimination prevents stack overflows by preventing the call stack from growing. CPython doesn’t implement TCO.
  • Memoization can speed up recursive algorithms by caching return values.

116 of 116

@AlSweigart

al@inventwithpython.com

bit.ly/nbpython2018recursion