Definition of "Memo"
The word "memoization" (Memo), introduced by Donald Michie in 1968, is based on the Latin word "memorandum", which means "to be remembered." Although it is somewhat similar to the word "memorization", it is not the wrong spelling of the word. In fact, memoisation is a technique used to speed up programs by calculating, by remembering the results of input calculations, such as the result of a function call, to achieve its accelerated purpose. If you encounter the same input or function calls with the same parameters, the previously stored results can be reused to avoid unnecessary computations. In many cases, you can use a simple array to store the results, but you can also use many other data structures, such as associative arrays, which are called hashes in the Perl language and are called dictionaries in the Python language.
The memo feature can be programmatically implemented by programmers, but some programming languages like Python provide a mechanism for automatic cheat functions.
using function Adorner to implement the function of the Cheat
In the previous chapter on recursive functions, we used iterations and recursion to achieve the solution of Fibonacci sequences. We have shown that if we use the mathematical definition of the Fibonacci sequence directly to solve the sequence in a recursive function, as in the following function, it will have exponential time complexity:
def fib (n):
if n = 0: return
0
elif n = 1: return
1
else: return
fib (n-1) + fib (n-2)
In addition, we propose a method to improve the time complexity of recursive implementation, that is, by adding a dictionary to remember the results of the previous function. This is an example of using the memo technique explicitly, but we didn't call it that at the time. The disadvantage of this approach is that the clarity and elegance of the original recursive implementation is lost.
The reason for the above disadvantage is that we have changed the code of recursive function fib. But the following code does not change our fib function, so its clarity and readability are not lost. To achieve this, we use the Custom function Memoize (). The function memoize () takes a function as a parameter and uses a dictionary "memo" to store the result of the function. Although the variable "memo" and Function "F" have only local memo functions, they are captured by a closure via the function "helper", while Memoize () returns the function "helper" as a reference. Therefore, a call to Memoize (FIB) will return a reference to the helper (). In the helper (), the function of the FIB () function and a wrapper for saving the result to be stored in the dictionary "memo" are implemented, and the results in "memo" are not recalculated.
Def memoize (f):
memo = {}
def helper (x):
If X to Memo:
memo[x] = f (x) return
memo[x]
return to He Lper
def fib (n):
if n = 0: return
0
elif n = 1: return
1
else: return
fib (n-1) + fib (n-2 )
fib = Memoize (fib)
print (FIB (40))
Now let's look at the so-called adorners, first of all, to see the line in the code that assigns the memo function to the FIB function:
One argument is that the function memoize () decorates the function fib.
encapsulate Memoize into a class
We can also encapsulate the cache of results into a class, as shown in the following example:
Class Memoize:
def __init__ (self, fn):
Self.fn = fn
Self.memo = {}
def __call__ (self, *args):
if Args not in Self.memo:
Self.memo[args] = Self.fn (*args) return
Self.memo[args]
Because we use a dictionary, we cannot use variable parameters, which means that the parameters must be immutable.
the adorner in Python
The adorner in Python is a callable Python object that modifies the definition of a function, method, or class. The original object, the object that is about to be changed, is passed as a parameter to an adorner, and the adorner returns a modified object, such as a modified function, which is bound to the name used in the definition. The adorner in Python has a similar syntax to the annotations in Java, that is, the adorner syntax in Python can be considered a pure syntactic sugar, using "@" as the keyword.
Example: using adorners to implement the memo feature
In fact, we have used the adorner before, just don't call it that. In fact, the Memoize function in the first example of this chapter is an adorner that we use to remember the result of the FIB function, except that we don't use the special syntax of the adorner in Python, that is, the AI character "@".
Compared to the form below
We can write this.
But this line must be written directly before the decorated function, in our example fib (), as follows:
Def memoize (f):
memo = {}
def helper (x):
If X to Memo:
memo[x] = f (x) return
memo[x]
return to He Lper
@memoize
def fib (n):
if n = 0: return
0
elif n = 1: return
1
else:
return FIB (n-1) + fib (n-2)
#fib = Memoize (fib)
print (FIB (40))
Using adorners to check parameters
In the chapter on recursive functions, we introduce the factorial function, where we want to keep the function as simple as possible without masking the basic idea, so the code does not contain any parameter checking code. However, if someone calls our function with negative numbers or floating-point numbers as arguments, then the function will fall into a dead loop.
The following program uses an adorner function to ensure that the argument passed to the function "factorial" is a positive integer:
Def argument_test_natural_number (f):
def Helper (x):
if Type (x) = = int and x > 0: Return
F (x)
else:
raise Exception ("Argument") return
helper
@argument_test_natural_number
def factorial (n):
if n = = 1: return
1
else: return
n * factorial (n-1)
to I in range (1,10):
print (I, fact Orial (i))
print (factorial (-1))
Practice
1, our practice is an ancient puzzle. In 1612, Claude-gaspar Bachet, a French Jesuit, presented the riddle, even though using a scale to weigh everything from 1 to 40 pounds (for example, sugar or flour), to find the least amount of weight.
The first method may be to use the weights of 1, 2, 4, 8, 16, and 32 pounds. If we put the weights at one end of the scale and put the items on the other side, then the amount of weight used in this method will be minimal. However, we can also put weights at both ends of the scale, at this time we only need weights of 1, 3, 9, 27 weight.
Write a Python function weigh () that calculates the weights needed and their distribution in the balance plate to weigh anything from 1 to 40 pounds for any integer weight.
Solving Method
1, we need the previous chapter "Linear combinations" in the function linear_combination ().
Def factors_set ():
Factors_set = (i,j,k,l) for I, [ -1,0,1] for
J, [ -1,0,1] for
k in [ -1,0,1] for
l I n [ -1,0,1]) for
factor in Factors_set:
yield factor
def memoize (f):
results = {}
def helper (n):
if n not in results:
results[n] = f (n) return
Results[n] return
helper
@memoize
def Linear_combination (N): "" "
Returns the tuple (i,j,k,l) satisfying
n = i*1 + j*3 + k*9 + l*27" ""
weigh s = (1,3,9,27) for
factors in Factors_set ():
sum = 0 for
i in range (len (factors)):
sum + = factors[i] * Weighs[i]
if sum = = N: return
factors
2, using the above code, it is easy to write our function weigh ().
def weigh (pounds):
weights = (1,3,9,27)
scalars = linear_combination (pounds) Left
= "Right
=
" For I in range (len (scalars)):
if scalars[i] = = 1: Left
+ + str (weights[i]) + "
elif scalars[i] = 1:
rig HT + + str (weights[i]) + "" Return
(left,right) to
i in [2,3,4,7,8,9,20,40]:
pans = weigh (i)
print (" Left pan: "+ str (i) +" plus "+ pans[0])
print (" Right pan: + pans[1] + "n")