Thoughts on A Beautiful Trick for Memorization
This trick is using a function argument to memorize recursive states of expensive calculations ( python docs ).
An example is to improve fibonacci calculator as follows,
def fib_naive(n): |
def fib_cache(n, _cache = {}): |
If you totally get the trick of _cache = {}
— isn’t it nice? — I am very happy to accept it.
Of course, maintaining a global variable for storage could also deliver the same functionality.
""" Using global cache """ |
I can not answer these questions with 100 percent confidence.
Let’s start with understanding local
and global
variables.
Local & Global
Python is compact. And with a price.
For example, it saves us from declaring the variables in a verbose way,
x = Point(10,10) # Python |
Point x = new Point(10, 10); /* Java */ |
const x = new Point(10, 10); // ECMA5 Javascript |
But it takes some efforts to understand its concept of variable scopes.
arr = [True] |
arr = [True] |
Let’s examine the differences.
In the first block, I am actually calling arr.__setitem__(0, False)
. Python finds out that arr
is not defined inside function scope (local
), and thus continues to look it up in the outside scope (global
). After successfully locating arr
in global scope, python calls a function to change the first item of a global variable.
In comparison, in the second block, python considers arr = [False]
as an action to create a local variable.
# < global variable scope > |
If we would like to add another element to arr
, there are two ways:
arr = [True] |
arr = [True] |
The first block is easy to understand, since calling a function arr.extend
is just like arr.__setitem__
in the previous example.
Meanwhile, in second block, arr += [False]
equals to arr = arr + [False]
.
One principle here in python is,
Anything being assigned (
x = ...
) is taken as a local variable infunction
.
At the left side of equation, on such principle, python judges arr
is a local variable. At the right side of equation, it throws an error because of a loop of variable assignment.
Our original purpose is to take arr
as a global variable. And we have to add a line to declare such intention.
arr = [True] |
arr = [True] |
Looking around on Javascript
// IT'S Javascript! |
// IT'S Javascript! |
In comparison, it’s easy to understand why javascript shocks so many engineers with non-front-end background, by placing more weight on easy to run
over global variable safety
. While in Baidu (one of the largest search companies, wiki), I have gained more than enough experience in debugging on web pages, where global variables are contaminated by careless coders.
Memorization
Does the cache speed up the calculation?
from timeit import default_timer as timer |
The Big O
of fib
is $O(2^n)$ (stackoverflow), while the fib_cache
is $O(n)$.
Memorization contributes a lot, and actually it is the core of Dynamic Programming. Some would like to introduce DP as,
And I think this equation explains Dynamic Programming better than the name itself.
Default Value
Consider following example:
def store(ele, arr = []): |
def store(ele, arr = None): |
As the Python docs points out that,
It is often expected that a function call creates new objects for default values. This is not what happens. Default values are created exactly once, when the function is defined.
// IT'S Javascript |
So a good programming practice is to NOT use mutable objects, e.g.list
/dict
, as default values. Immutable objects of default values are also created once, since it can not be changed, the values will be copied to create new local variables inside the function.
Unless you are very clear about what you are doing, mutable objects of default values can cause confusing consequences.
Looking at fib_cache(..., _cache = {})
again, it takes full advantage of such feature, _cache
is bound to this function, serving as a function-level shared variable. It reminds me of class variable shared by all instances.
I will end this post by improving the global solution as mentioned in the beginning.
""" Global variable solution """ |