私はPythonを始めたばかりで、 memoization とは何か、そしてそれをどのように使うのかわかりません。また、簡単な例がありますか。
メモ化とは、メソッド入力に基づいてメソッド呼び出しの結果を覚えて(「メモ」→「メモ」→覚えておく)、結果を再度計算するのではなく返すことを意味します。あなたはそれをメソッド結果のキャッシュと考えることができます。詳細については、アルゴリズムの紹介(3e)のCormen et alの定義について387ページを参照してください。 al。
Pythonでメモ化を使用して階乗を計算する簡単な例は、次のようになります。
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)
" decorators "として知られる機能がPython 2.4で追加されました。これにより、同じことを実現するために次のように書くことができます。
@Memoize
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
Python Decorator Library には memoized
という同様のデコレータがあります。これは、ここに示すMemoize
クラスよりも若干堅牢です。
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)
を試してみてください。約10秒待つ必要があります。
lru_cache
アノテーションを追加すると、関数が特定の値に対して最近呼び出された場合、その値を再計算するのではなく、以前にキャッシュされた結果を使用します。この場合、コードがキャッシュの詳細に散らばっていない一方で、それは途方もない速度の向上につながります。
他の答えはそれがかなりうまくいっていることをカバーしています。私はそれを繰り返さない。あなたに役立つかもしれないちょうどいくつかの点。
通常、メモ化は、何かを計算し(高価な)、値を返す任意の関数に適用できる操作です。このため、しばしば デコレータ として実装されています。実装は簡単で、これは次のようになります。
memoised_function = memoise(actual_function)
またはデコレータとして表現
@memoise
def actual_function(arg1, arg2):
#body
メモ化は、継続的に計算し直すのではなく、高価な計算の結果を保持してキャッシュ結果を返すことです。
これが例です:
def doSomeExpensiveCalculation(self, input):
if input not in self.cache:
<do expensive calculation>
self.cache[input] = result
return self.cache[input]
より完全な説明は、 メモ化に関するウィキペディアのエントリ にあります。
手作りしたい人のために、組み込みの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]
私はこれが非常に便利だと思った
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)
メモ化は基本的に、同じ計算が後の段階で必要とされる場合に再帰木を横断する必要性を減らすために再帰的アルゴリズムで行われた過去の操作の結果を保存することです。
参照してください http://scriptbucket.wordpress.com/2012/12/11/introduction-to-memoization/
Pythonのフィボナッチメモ化の例:
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]
さて私は最初の部分に最初に答えるべきです:何がメモ化ですか?
時間のためにメモリを交換するための方法です。考えてください 乗算テーブル 。
Pythonでデフォルト値として可変オブジェクトを使用することは通常悪いと見なされます。しかし、それを賢く使用するのであれば、実際にはmemoization
を実装すると便利です。
これは http://docs.python.org/2/faq/design.html#why-are-default-values-shared-between-objects から改作した例です。
関数定義で可変の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]
メモ化とは、関数をデータ構造に変換することです。通常、変換は段階的かつ遅延的に行われることが望まれます(特定のドメイン要素または「キー」の要求に応じて)。遅延機能言語では、この遅延変換は自動的に行われる可能性があるため、メモ化は(明示的な)副作用なしに実装できます。
これは泣き言を言わずにリスト型または辞書型の引数を扱う解決策です:
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.__= 'memoized_' + fn.__name__
return foo
独自のハッシュ関数をhandle_itemの特別なケースとして実装することで、このアプローチは任意のオブジェクトに自然に拡張できることに注意してください。たとえば、入力引数として集合を取る関数に対してこのアプローチを機能させるには、handle_itemに追加します。
if is_instance(x, set):
return make_Tuple(sorted(list(x)))
キーワード引数が渡された順序とは無関係に、位置引数とキーワード引数の両方を扱うソリューション( 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
同様の質問: Pythonでメモ化を行うための同等の可変引数関数の識別
functools.lru_cache
とは異なり、 Pythonデコレータライブラリ はすでに提供されている回答に追加したいだけで、「手に負えない型」を記憶することもできる単純でありながら便利な実装をいくつか備えています。
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]