我刚开始使用Python,我不知道memoization是什么以及如何使用它。另外,我可以有一个简化的例子吗?
答案 0 :(得分:325)
记忆有效地指基于方法输入记住(“记忆”→“备忘录”→记忆)方法调用的结果,然后返回记住的结果而不是再次计算结果。您可以将其视为方法结果的缓存。有关详细信息,请参阅第387页,了解 算法简介 (3e)中的定义,Cormen等人
使用Python中的memoization计算阶乘的简单示例如下:
factorial_memo = {}
def factorial(k):
if k < 2: return 1
if k not in factorial_memo:
factorial_memo[k] = k * factorial(k-1)
return factorial_memo[k]
您可以将更复杂的内容和封装过程封装到一个类中:
class Memoize:
def __init__(self, f):
self.f = f
self.memo = {}
def __call__(self, *args):
if not args in self.memo:
self.memo[args] = self.f(*args)
#Warning: You may wish to do a deepcopy here if returning objects
return self.memo[args]
然后:
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
factorial = Memoize(factorial)
Python 2.4中添加了一个名为“decorators”的功能,现在您只需编写以下内容即可完成相同的操作:
@Memoize
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
Python Decorator Library有一个名为memoized
的类似装饰器,它比这里显示的Memoize
类稍强一些。
答案 1 :(得分:196)
Python 3.2的新功能是functools.lru_cache
。默认情况下,它仅缓存最近使用的128个调用,但您可以将maxsize
设置为None
以指示缓存永远不会过期:
import functools
@functools.lru_cache(maxsize=None)
def fib(num):
if num < 2:
return num
else:
return fib(num-1) + fib(num-2)
此功能本身非常慢,尝试fib(36)
,您将不得不等待大约十秒钟。
添加lru_cache
注释可确保如果最近为特定值调用了函数,则不会重新计算该值,而是使用缓存的先前结果。在这种情况下,它会带来巨大的速度提升,而代码不会被缓存的细节混乱。
答案 2 :(得分:59)
其他答案涵盖了很好的内容。我不是在重复。只是一些可能对您有用的要点。
通常,memoisation是一种可以应用于计算某些东西(昂贵)并返回值的任何函数的操作。因此,它通常被实现为decorator。实现很简单,就像这样
memoised_function = memoise(actual_function)
或表达为装饰者
@memoise
def actual_function(arg1, arg2):
#body
答案 3 :(得分:18)
记忆是保持昂贵计算的结果并返回缓存结果而不是连续重新计算它。
以下是一个例子:
def doSomeExpensiveCalculation(self, input):
if input not in self.cache:
<do expensive calculation>
self.cache[input] = result
return self.cache[input]
可以在wikipedia entry on memoization中找到更完整的说明。
答案 4 :(得分:14)
让我们不要忘记那些想要手工制作的内置hasattr
功能。这样,您可以将mem缓存保留在函数定义中(而不是全局)。
def fact(n):
if not hasattr(fact, 'mem'):
fact.mem = {1: 1}
if not n in fact.mem:
fact.mem[n] = n * fact(n - 1)
return fact.mem[n]
答案 5 :(得分:9)
我发现这非常有用
def memoize(function):
from functools import wraps
memo = {}
@wraps(function)
def wrapper(*args):
if args in memo:
return memo[args]
else:
rv = function(*args)
memo[args] = rv
return rv
return wrapper
@memoize
def fibonacci(n):
if n < 2: return n
return fibonacci(n - 1) + fibonacci(n - 2)
fibonacci(25)
答案 6 :(得分:6)
Memoization基本上保存了使用递归算法完成的过去操作的结果,以便在后期需要相同的计算时减少遍历递归树的需要。
请参阅http://scriptbucket.wordpress.com/2012/12/11/introduction-to-memoization/
Python中的Fibonacci Memoization示例:
fibcache = {}
def fib(num):
if num in fibcache:
return fibcache[num]
else:
fibcache[num] = num if num < 2 else fib(num-1) + fib(num-2)
return fibcache[num]
答案 7 :(得分:5)
记忆是将函数转换为数据结构。通常,人们希望转换以递增和懒惰的方式发生(根据给定域元素的要求 - 或“密钥”)。在惰性函数语言中,这种惰性转换可以自动发生,因此可以在没有(显式)副作用的情况下实现memoization。
答案 8 :(得分:5)
我应该首先回答第一部分:什么是记忆?
这只是一种交换记忆的方法。想想Multiplication Table。
在Python中使用可变对象作为默认值通常被认为是错误的。但如果明智地使用它,实现memoization
实际上是有用的。
以下是改编自http://docs.python.org/2/faq/design.html#why-are-default-values-shared-between-objects
的示例在函数定义中使用mutable dict
,可以缓存中间计算结果(例如,在计算factorial(10)
之后计算factorial(9)
时,我们可以重用所有中间结果)
def factorial(n, _cache={1:1}):
try:
return _cache[n]
except IndexError:
_cache[n] = factorial(n-1)*n
return _cache[n]
答案 9 :(得分:4)
这是一个解决方案,可以使用list或dict类型的参数而不需要抱怨:
def memoize(fn):
"""returns a memoized version of any function that can be called
with the same list of arguments.
Usage: foo = memoize(foo)"""
def handle_item(x):
if isinstance(x, dict):
return make_tuple(sorted(x.items()))
elif hasattr(x, '__iter__'):
return make_tuple(x)
else:
return x
def make_tuple(L):
return tuple(handle_item(x) for x in L)
def foo(*args, **kwargs):
items_cache = make_tuple(sorted(kwargs.items()))
args_cache = make_tuple(args)
if (args_cache, items_cache) not in foo.past_calls:
foo.past_calls[(args_cache, items_cache)] = fn(*args,**kwargs)
return foo.past_calls[(args_cache, items_cache)]
foo.past_calls = {}
foo.__name__ = 'memoized_' + fn.__name__
return foo
请注意,通过将自己的哈希函数实现为handle_item中的特殊情况,可以自然地将此方法扩展到任何对象。例如,要使此方法适用于将set作为输入参数的函数,可以添加到handle_item:
if is_instance(x, set):
return make_tuple(sorted(list(x)))
答案 10 :(得分:3)
与位置和关键字参数一起使用的解决方案,与传递关键字args的顺序无关(使用inspect.getargspec):
import inspect
import functools
def memoize(fn):
cache = fn.cache = {}
@functools.wraps(fn)
def memoizer(*args, **kwargs):
kwargs.update(dict(zip(inspect.getargspec(fn).args, args)))
key = tuple(kwargs.get(k, None) for k in inspect.getargspec(fn).args)
if key not in cache:
cache[key] = fn(**kwargs)
return cache[key]
return memoizer
类似的问题:Identifying equivalent varargs function calls for memoization in Python
答案 11 :(得分:2)
cache = {}
def fib(n):
if n <= 1:
return n
else:
if n not in cache:
cache[n] = fib(n-1) + fib(n-2)
return cache[n]
答案 12 :(得分:2)
只是想添加已经提供的答案,Python decorator library有一些简单但有用的实现,也可以记住&#34;不可用的类型&#34;,与functools.lru_cache
不同。