This article describes how to use the function decorator to implement the memo function in Python. it also reduces the usage of the decorator to check the recursion of the function and ensure that the parameter is passed correctly. For more information, see
Definition of "memo"
The word "memoization" was proposed by Donald Michie in 1968. it is based on the Latin word "memorandum" (memorandum), meaning "remembered ". Although it is somewhat similar to the word "memorization", it is not a misspelling of the word. In fact, Memoisation is a technology used to accelerate a program through computing. it remembers the calculation results of input values, such as the results of function calls, to accelerate the program. If the same input or function call with the same parameters is encountered, the previously stored results can be used again to avoid unnecessary computation. In many cases, you can use a simple array to store results, but you can also use many other data structures, such as Associated arrays, which are called hashing in Perl, it is called a dictionary in Python.
The memo function can be explicitly programmed by the programmer, but some programming languages such as Python all provide an automatic memo function mechanism.
Implement the memo function using the function decorator
In the previous chapter on recursive functions, we use iteration and recursion to solve the Fibonacci series respectively. We have proved that if we use the mathematical definition of the Fibonacci sequence to solve the sequence in a recursive function, just like 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 also propose a method to improve the time complexity of recursive implementation, that is, by adding a dictionary to remember the calculation results of previous functions. This is an example of the explicit use of the memo technology, but we didn't call it that at the time. The disadvantage of this method is that the clarity and elegance of original recursion are lost.
The reason for the above disadvantage is that we changed the code of the recursive function fib. However, the following code does not change our fib function, so its clarity and ease of use are not lost. To achieve this, we use the custom function memoize (). The memoize () function uses the function as a parameter and uses the dictionary "memo" to store the function results. Although the variables "memo" and function "f" only have partial memo functions, they are captured by a closure through the function "helper", while memoize () return the function "helper" as a reference. Therefore, the call to memoize (fib) will return a reference to helper (), while the implementation of fib () in helper () function and a wrapper used to save unstored results to the dictionary "memo" and prevent recalculation of existing results in "memo.
def memoize(f): memo = {} def helper(x): if x not in memo: memo[x] = f(x) return memo[x] return helper 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 take a look at the so-called Decorator. First, let's take a look at the line in the code above that assigns the memo function to the fib function:
fib = memoize(fib)
One way is that the function memoize () is decorated with the function fib.
Package Memoize into a class
We can also encapsulate the result cache 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 the dictionary is used, variable parameters cannot be used, that is, the parameters must be immutable.
Decorator in Python
The decorator in Python is an callable Python object used to modify the definition of a function, method, or class. The original object, that is, the object to be changed, is passed as a parameter to a decorator, and the decorator returns a modified object, such as a modified function, it is bound to the name used in the definition. The decorator in Python has a similar syntax with the annotation in Java, that is, the decorator syntax in Python can be viewed as a pure syntactic sugar, using "@" as the keyword.
Example: Use the decorator to implement the memo function
As a matter of fact, we have already used a decorator, but we have not called it that. In fact, the memoize function in the example at the beginning of this chapter is a decorator. We use it to remember the results of the fib function, but we do not use the special syntax of the Python modifier, that is, the Aite character "@".
Compared with the following format:
fib = memoize(fib)
We can write it like this
@memoize
However, this line must be written directly before the decorated function. In our example, fib (), as shown below:
def memoize(f): memo = {} def helper(x): if x not in memo: memo[x] = f(x) return memo[x] return helper @memoizedef 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))
Use the decorator to check parameters
In the chapter that explains recursive functions, we introduce factorial functions, where we want to keep functions as simple as possible without hiding the basic concepts, so the code does not contain any parameter check code. However, if someone calls our function using a negative number or floating point number as a parameter, the function will be in an endless loop.
The following program uses a modifier function to ensure that the parameter 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 is not an integer") return helper @argument_test_natural_numberdef factorial(n): if n == 1: return 1 else: return n * factorial(n-1) for i in range(1,10): print(i, factorial(i)) print(factorial(-1))
Exercise
1. our exercises are an old puzzle. In 1612, the French Jesus society clude-Gaspar Bachet proposed the puzzle, that is, using a balance to name all integer weights (such as sugar or flour) from 1 to 40 ), calculate the minimum weight.
The first method may be to use the weights of 1, 2, 4, 8, 16, and 32 lbs. If we place the weight on one end of the balance and the item on the other end, the weight used in this method will be the smallest. However, we can also place the weight at both ends of the balance. at this time, we only need a weight of 1, 3, 9, and 27.
Compile a Python function weigh () that computes the required weights and their distribution on the flatdisk to weigh any integer from 1 to 40.
Solution
1. we need the linear_combination () function in the previous section "Linear Combinations ().
def factors_set(): factors_set = ( (i,j,k,l) for i in [-1,0,1] for j in [-1,0,1] for k in [-1,0,1] for l in [-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 @memoizedef linear_combination(n): """ returns the tuple (i,j,k,l) satisfying n = i*1 + j*3 + k*9 + l*27 """ weighs = (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, we can easily write out 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: right += str(weights[i]) + " " return (left,right) for 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")