**Definitions of "Memo"**
The word "memoization" was proposed by Donald Michie in 1968, based on the Latin word "Memorandum" (memo), meaning "to be remembered". Although it is somewhat similar to the word "memorization", it is not a misspelling of the word. In fact, memoisation is a technique used to speed up a program through computation, and it achieves its accelerating purpose by remembering the results of the input, such as the result of a function call. If you encounter the same input or a function call 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 function can be programmed explicitly by programmers, but some programming languages, such as Python, provide mechanisms for automatic memo functions.

**Using function adorner to realize the function of memo and forget**

In the previous chapter on recursive functions, we used iterations and recursion to solve the Fibonacci sequence. We have shown that if the mathematical definition of a Fibonacci sequence is directly used to solve a 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 implementations by adding a dictionary to remember the results of previous functions. 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 these drawbacks 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 legibility are not lost. For this purpose, we use the Custom function Memoize (). The function memoize () takes a function as an argument and uses a dictionary "memo" to store the result of the function. Although the variable "memo" and the function "F" have only local memo functions, they are captured by a closure by the function "helper", and Memoize () returns the function "helper" as a reference. Therefore, the call to Memoize (FIB) will return a reference to the helper (), which implements the function of the FIB () function in helper () and a wrapper in the dictionary "memo" to hold the results that have not yet been stored, and prevents recalculation of the results already in the memo.

Def memoize (f): memo = {} def helper (x): if X not in memo: memo[x] = f (x) return memo[x] return 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 take a look at the so-called adorner, first of all, to see the line in the above code that assigns the memo function to the FIB function:

FIB = Memoize (FIB)

One argument is that the function memoize () decorates the function fib.

**Encapsulating the memoize into classes**

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 mutable parameters, that is, the parameters must be immutable.

**adorners 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, where the adorner syntax in Python can be thought of as purely syntactic sugar, using "@" as the keyword.

**Example: using adorners to implement the memo function**

In fact, we have used the adorner in front of us, but we don't call it that. In fact, the Memoize function in the first example of this chapter is an adorner, which we use to remember the result of the FIB function, except that we don't use the special syntax of the adorner in Python, the "@" character of the AI.

Compared to the following form

FIB = Memoize (FIB)

We can write that.

@memoize

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 not in memo: memo[x] = f (x) return memo[x] return he Lper @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))

**checking parameters with adorners**
In the chapter explaining the recursive function we introduced the factorial function, where we wanted to keep the function as simple as possible without masking the basic idea, so the code did not contain any parameter check code. However, if someone calls our function with a negative number or a floating-point number as a parameter, 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 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 (FA Ctorial (-1))

**Practice**

1, our practice is an ancient puzzle. In 1612, the French Jesuit Claude-gaspar Bachet proposed the puzzle, even if a balance was used to weigh everything from 1 pounds to 40 pounds (for example, sugar or flour) for the minimum amount of weight.

The first method may be these weights using 1, 2, 4, 8, 16, and 32 lb weights. If we put the weights at one end of the balance and put the items on the other end, the amount of weights used for this method will be minimal. However, we can also put weights at both ends of the balance, at which point we only need weights of 1, 3, 9, 27.

Write a python function, weigh (), which calculates the required weights and their distribution in the balance plate, to weigh items from 1 pounds to 40 pounds of any integer weight.

**Workaround**

1. We need the function linear_combination () 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 I n [ -1,0,1]) for factor in Factors_set: yield factor def memoize (f): results = {} def helper (n): if n n OT 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) fo R 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) for i in [2,3,4,7,8,9,20,40]: pans = weigh (i) print ("left PA N: "+ str (i) +" plus "+ pans[0]) print (" Right pan: "+ pans[1] +" n ")

**