Week 11: Recursion
Recursion
Any function that sometimes calls itself is known as a recursive function. In most cases, recursion can be considered an alternative to iteration (loops). Instead of defining a loop structure, recursion defines a problem as a smaller problem until it reaches an end point. There are three requirements for a successful recursive function:

Base case: each function must have one or more base cases where the function does not make a recursive call to itself. Instead, the problem has reached its smallest point, and we can begin returning back the small solutions.

Recursive case: the function makes one (or more) calls to itself. The call must use a different argument to the function (or else we keep making the same recursive call over and over again!). Typically, the problem gets 1 smaller.

All possible recursive calls must be guaranteed to hit a base case. If you miss one, your program is susceptible to an infinite recursion bug!
Example: Print a Message
The printmessage.py
program prints a given message n
times. We’ve already
seen how to solve this problem with a loop (this is known as an iterative
solution, one that uses a loop to repeat). However, let’s explore how we
can solve it without a loop using recursion.
To think about how to define printing a message n
times in a recursive
way, we want to define printing in terms of printing. In other words we
want to think of a recursive definition of printing n things, and our
recursive function’s structure will follow this with it recursive function
calls:
print_message(msg, num_times) is equivalent to:
1. print(msg)
2. print_message(msg, num_times1) # here is our recursive definition
To understand what’s happening with recursion, it’s often helpful to draw a stack diagram. The key concept is that Python treats the recursive function call the same as any other function call — it places a new stack frame on top and begins executing the new function. The only difference is the function in this case has the same name.
Examples with slicing
When developing recursive solutions, it helps to think about how to solve a big problem by

solving a smaller problem of the exact same form (the recursive part)

doing a small amount of work (combining the recursive result with a little extra info)
Many functions on lists can be solved recursively. We can break a large list into smaller pieces using slicing.
In length_sol.py
and max_sol.py
, we use slicing to make a smaller list. We solve the problem recursively on the smaller list and combine the recursive solution with a litte bit of extra work to solve the original, bigger problem.