Recursion is a technique in which a function repeatedly calls itself until a base case (termination condition) is met. It breaks the problem into smaller sub-problems and calls itself until it reaches the slightest problem (solution already known; for example, factorial(1) = 1) is also known as the base case.
Memoization is a top-down dynamic programming approach in which the results of all the recursive calls are recorded/cached. If the function is again called with the same parameters, it uses the stored results to avoid repeated calculations and speed up the program.
In this Answer, we will use the example of the Fibonacci series to explain the concept of repeated calculations using recursion.
The recursive code used to calculate a Fibonacci number is as follows:
# Fibonacci Numbersdef fib(num):if num == 0:return 0elif num == 1:return 1return fib(num - 1) + fib(num - 2)print(fib(6))
The explanation for this code is as follows:
Line 3: It is a base case because the
Line 5: It is also a base case because the
Line 7: It makes two recursive calls to calculate the last two Fibonacci numbers, adds them both, and returns this number.
This code is not very efficient because we are making too many repetitive recursive calls with the same inputs, as shown in the diagram below:
We can reduce these repetitive calls using a dynamic programming technique called memoization.
Memoization is implemented using:
Decorators in Python
The functiontools.lru_cache
function
A cache is used to store the data in the code snippet below. If the required data is already computed, it returns its output. Otherwise, it calls the required function:
def memoize_fib(func):cache = {}def inner(arg):if arg not in cache:cache[arg] = func(arg)return cache[arg]return inner
The code for this technique is as follows:
import timeitdef memoize_fib(func):cache = {}def inner(arg):if arg not in cache:cache[arg] = func(arg)return cache[arg]return inner@memoize_fibdef fib(num):if num == 0:return 0elif num == 1:return 1else:return fib(num-1) + fib(num-2)print( "30th Fibonacci number is:",fib(30))print("Time taken to run this function is:",timeit.timeit('fib(30)', globals=globals(), number=1))
Lines 3–9: We define a function that will later be used as a decorator. It caches the result of our previous function calls, and if the result is already present in the cache, then it returns that result.
Line 11: It declares that the following function must be used with the memoize_fib
decorator.
Lines 12–18: We define a normal recursive function to calculate a Fibonacci number.
Note: The
timeit()
function calculates the execution time for our code. For more information, click here.
functiontools.lru_cache
function in PythonIt is a built-in decorator that is available in the functools
library of Python. It uses the memoization technique to reduce the execution time of a function.
The code for this technique is as follows:
import functoolsimport timeit@functools.lru_cache(maxsize=150)def fib(num):if num == 0:return 0elif num == 1:return 1return fib(num - 1) + fib(num - 2)print("30th Fibonacci number is:", fib(30))print("Time taken to run this function:", timeit.timeit('fib(30)', globals=globals(), number=1))
The explanation for the code above is as follows:
Line 4: It is a decorator declared in functools
library. It caches the results from previous function calls and returns that result if the function is called with the same parameters again.
Lines 5–10: Defines a simple recursive function to calculate a Fibonacci number.