GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing inventwithpython.com — Chapter 7 - Memoization and Dynamic Programming Check out other books by Al Sweigart, free online or available for purchase: ...and other books as well! Or register for the online video course. You can also donate to support the author directly.
In this chapter, we’ll explore memoization, a technique for making recursive algorithms run faster. We’ll discuss what memoization is, how it should be applied, and its usefulness in the areas of functional programming and dynamic programming. We’ll use the Fibonacci algorithm from Chapter 2 to demonstrate memoizing code we write and the memoization features we can find in the Python standard library. We’ll also learn why memoization can’t be applied to every recursive function. MemoizationMemoization is the technique of remembering the return values from a function for the specific arguments supplied to it. For example, if someone asked me to find the square root of 720, which is the number that when multiplied by itself results in 720, I’d have to sit down with pencil and paper for a few minutes (or call Confusing memoization with memorization is a modern mistake made by many. (Feel free to make a memo to remind yourself of the difference.) Top-Down Dynamic ProgrammingMemoization is a common strategy in dynamic programming, a computer programming technique that involves breaking a large problem into overlapping subproblems. This might sound a lot like the ordinary recursion we’ve already seen. The key difference is that dynamic programming uses recursion with repeated recursive cases; these are the overlapping subproblems. For example, let’s consider the recursive Fibonacci algorithm from Chapter 2. Making a recursive An inefficiency exists here: performing those same calculations multiple times is unnecessary, because Figure 7-1 shows a tree diagram of all the recursive calls, including the redundant function calls that memoization can optimize. Meanwhile, quicksort and merge sort are recursive divide-and-conquer algorithms, but their subproblems do not overlap; they are unique. Dynamic programming techniques aren’t applied to these sorting algorithms.
Figure 7-1: A tree diagram of the recursive function calls made starting with One approach in dynamic programming is to memoize the recursive function so that previous calculations are remembered for future function calls. Overlapping subproblems become trivial if we can reuse previous return values. Using recursion with memoization is called top-down dynamic programming. This process takes a large problem and divides it into smaller overlapping subproblems. A contrasting technique, bottom-up dynamic programming, starts with the smaller subproblems (often related to the base case) and “builds up” to the solution of the original, large problem. The iterative Fibonacci algorithm, which begins with the base cases of the first and second Fibonacci numbers, is an example of bottom-up dynamic programming. Bottom-up approaches don’t use recursive functions. Note that there is no such thing as top-down recursion or bottom-up recursion. These are commonly used but incorrect terms. All recursion is already top-down, so top-down recursion is redundant. And no bottom-up approach uses recursion, so there’s no such thing as bottom-up recursion. Memoization in Functional ProgrammingNot all functions can be memoized. To see why, we must discuss functional programming, a programming paradigm that emphasizes writing functions that don’t modify global variables or any external state (such as files on the hard drive, internet connections, or database contents). Some programming languages, such as Erlang, Lisp, and Haskell, are heavily designed around functional programming concepts. But you can apply functional programming features to almost any programming language, including Python and JavaScript. Functional programming includes the concepts of deterministic and nondeterministic functions, side effects, and pure functions. The Side effects are any changes a function makes to anything outside of its own code and local variables. To illustrate this, let’s create a Python
This Now consider an Python
The Side effects can be more than mere changes to global variables. They include updating or deleting files, printing text onscreen, opening a database connection, authenticating to a server, or any other manipulation of data outside of the function. Any trace that a function call leaves behind after returning is a side effect. If a function is deterministic and has no side effects, it’s known as a pure function. Only pure functions should be memoized. You’ll see why in the next sections when we memoize the recursive Fibonacci function and the impure functions of the Memoizing the Recursive Fibonacci AlgorithmLet’s memoize our recursive Fibonacci function from Chapter 2. Remember that this function is extraordinarily inefficient: on my computer, the recursive Memoization can greatly speed up the recursive version of the function. For example, Figure 7-2 shows the number of function calls the original and memoized The number of function calls sharply increases for the original ![]()
Figure 7-2: The number of function calls sharply increases for the original fibonacci() function (top) but grows only slowly for the memoized fibonacci() function (bottom). The Python version of the memoized Fibonacci algorithm is in fibonacciByRecursionMemoized.py. The additions to the original fibonacciByRecursion.html program from Chapter 2 have been marked in bold:
The JavaScript version of the memoized Fibonacci algorithm is in fibonacciByRecursionMemoized.html. The additions to the original fibonacciByRecursion.html program from Chapter 2 have been marked in bold: JavaScript
If you compare the output of this program to the original recursive Fibonacci program in Chapter 2, you’ll find it’s much shorter. This reflects the massive reduction of computation needed to achieve the same results:
To memoize this function, we’ll create a dictionary (in Python) or object (in JavaScript) in a global variable named The memoized function is effectively expanding the number of base cases in the Fibonacci algorithm. The original base cases are only for the first and second Fibonacci numbers: they immediately return Keep in mind that while memoization can reduce the number of redundant function calls a recursive algorithm makes, it doesn’t necessarily reduce the growth of frame objects on the call stack. Memoization won’t prevent stack overflow errors. Once again, you may be better off forgoing a recursive algorithm for a more straightforward iterative one. Python’s functools ModuleImplementing a cache by adding a global variable and code to manage it for every function you’d like to memoize can be quite a chore. Python’s standard library has a The cache can have a memory size limit set. The lru in the decorator name stands for the least recently used cache replacement policy, meaning that the least recently used entry is replaced with new entries when the cache limit is reached. The LRU algorithm is simple and fast, though other cache replacement policies are available for different software requirements. The fibonacciFunctools.py program demonstrates the use of the Python
Compared to the additions required to implement our own cache in fibonacciByRecursionMemoized.py, using Python’s Memoization is a useful technique for recursive functions with overlapping subproblems, but it can be applied to any pure function to speed up runtime at the expense of computer memory. What Happens When You Memoize Impure Functions?You should not add the In nondeterministic functions, such as a function that returns the current time, memoization causes the function to return incorrect results. For functions with side effects, such as printing text to the screen, memoization causes the function to skip the intended side effect. The doNotMemoize.py program demonstrates what happens when the Python
When you run this program, the output looks like this:
Notice that the second call to These bugs are subtle because they don’t cause an obvious crash, but rather cause the functions to behave incorrectly. No matter how you memoize your functions, be sure to thoroughly test them. SummaryMemoization (not memorization) is an optimization technique that can speed up recursive algorithms that have overlapping subproblems by remembering the previous results of identical calculations. Memoization is a common technique in the field of dynamic programming. By trading computer memory usage for improved runtime, memoization makes some otherwise intractable recursive functions possible. However, memoization won’t prevent stack overflow errors. Keep in mind that memoization is not a replacement for using a simple iterative solution. Code that uses recursion for the sake of recursion is not automatically more elegant than non-recursive code. Memoized functions must be pure—that is, they must be deterministic (returning the same values given the same arguments each time) and not have side effects (affecting anything about the computer or program outside of the function). Pure functions are often used in functional programming, which is a programming paradigm that makes heavy use of recursion. Memoization is implemented by creating a data structure called a cache for each function to memoize. You can write this code yourself, but Python has a built-in Further ReadingThere’s more to dynamic programming algorithms than simply memoizing functions. These algorithms are often used in both coding interviews and programming competitions. Coursera offers a free “Dynamic Programming, Greedy Algorithms” course at https://www.coursera.org/learn/dynamic-programming-greedy-algorithms. The freeCodeCamp organization also has a series on dynamic programming at https://www.freecodecamp.org/news/learn-dynamic-programing-to-solve-coding-challenges. If you’d like to learn more about the LRU cache and other cache-related functions, the official Python documentation for the Practice QuestionsTest your comprehension by answering the following questions:
|
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |