メモ化とカリー化(Python)

こんにちは、Habrahabrの親愛なる読者。 この記事では、メモ化とカリー化とは何か、これらのメソッドがPython標準ライブラリでどのように実装されるかを理解しようとします。



メモ化



これは、関数実行の結果が保存され、この結果が次の呼び出しで使用される最適化方法です。



フィボナッチ数を見つける再帰的な実装を使用して、実行時間を調べます。



@clock def fib(n): if n < 2: return n return fib(n-2) + fib(n-1) print('fib(20) =', fib(20))
      
      





 [0.35938287s] fib(20) -> 6765
      
      





@clock
 def clock(func): def clocked(*args, **kwargs): t0 = time.time() result = func(*args, **kwargs) #    elapsed = time.time() - t0 name = func.__name__ arg_1st = [] if args: arg_1st.append(', '.join(repr(arg) for arg in args)) if kwargs: pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())] arg_1st.append(', '.join(pairs)) arg_str = ', '.join(arg_1st) print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result)) return result return clocked
      
      







画像



検出する必要がある数が増えると、ランタイムは非常に急速に成長し、RecursionErrorエラーが発生する可能性があります。



このようなアルゴリズムを最適化するには、メモ化メソッドが適しています。つまり、初期の計算値を保存して再利用します(ただし、もちろん、最初から再帰アルゴリズムを完全に放棄する必要があります)。



 _fib_cache = {1: 1, 2: 1} #  -  ,  -   @clock def mem_fib(n): result = _fib_cache.get(n) if result is None: result = mem_fib(n-2) + mem_fib(n-1) _fib_cache[n] = result return result print('mem_fib(200) =', mem_fib(200))
      
      





 [0.03125453s] mem_fib(200) -> 280571172992510140037611932413038677189525
      
      





またはデコレーターとして:



 def memoize(f): cache = {} def decorate(*args): if args in cache: return cache[args] else: cache[args] = f(*args) return cache[args] return decorate #     lambda # def memoize(f): # cache = {} # return lambda *args: cache[args] if args in cache else cache.update({args: f(*args)}) or cache[args] #       # def memoize(f): # cache = {} # # def decorate(*args, **kwargs): # key = (tuple(args), hash(tuple(sorted(kwargs.items())))) # if key not in cache: # cache[key] = f(*args, **kwargs) # return cache[key] # # return decorate @clock @memoize def fib(n): if n < 2: return n return fib(n-2) + fib(n-1) print('fib(20) =', fib(20))
      
      





良いニュースは、lru_cacheデコレータが標準のfunctoolsライブラリに既に十分に実装されていることです。



LRUは、Least Recent Usedの略です。



 from functools import lru_cache @clock @lru_cache() def fib(n): if n < 2: return n return fib(n-2) + fib(n-1) print('fib(20) =', fib(20))
      
      





lru_cacheには2つのオプションの引数があります。

maxsizeは、保存された結果の数です。

typed-trueの場合、たとえば、値1と1.0は異なると見なされます。



メモ化は非常にシンプルで効果的な方法です。 functools.lru_cacheのおかげで、Pythonで使用すると便利です。 内部では、ハッシュテーブル形式の辞書がそれぞれあり、キーはハッシュを実装する必要があります。



カレーまたはカレーについて



カリー化とは、多くの引数から関数のセットへの関数の変換であり、各関数は1つの引数の関数です。 引数の一部を関数に渡し、残りの引数を期待する関数を取得できます。



挨拶と名前を引数として取る簡単な挨拶関数を作成しましょう。



 def greet(greeting, name): print(greeting + ', ' + name) greet('Hello', 'German')
      
      





少し改善することで、あらゆる種類のグリーティング用の新しい関数を作成し、この新しい関数に名前を渡すことができます。



 def greet_curried(greeting): def greet(name): print(greeting + ', ' + name) return greet greet_hello = greet_curried('Hello') greet_hello('German') greet_hello('Ivan') #   greet_curried greet_curried('Hi')('Roma')
      
      





そして、任意の数の引数でこれを行うことができます:



 def greet_deeply_curried(greeting): def w_separator(separator): def w_emphasis(emphasis): def w_name(name): print(greeting + separator + name + emphasis) return w_name return w_emphasis return w_separator greet = greet_deeply_curried("Hello")("...")(".") greet('German') greet('Ivan')
      
      





または、ラムダを持つバリアント:



 greet_deeply_curried = lambda greeting: lambda separator: lambda emphasis: lambda name: \ print(greeting + separator + name + emphasis)
      
      







部分適用



これは、引数の一部に関数を適用するプロセスです。

言い換えると、いくつかのパラメーターを持つ関数を取り、より少ないパラメーターを持つ関数を返す関数です。



部分的なアプリケーションは、関数をn個の引数から(xn)に変換し、カリー化は1個の引数を持つn個の関数を作成します。



Pythonは標準のfunctoolsライブラリにこのような機会があります。これは関数です

部分的。



 from functools import partial def greet(greeting, separator, emphasis, name): print(greeting + separator + name + emphasis) newfunc = partial(greet, greeting='Hello', separator=',', emphasis='.') newfunc(name='German') newfunc(name='Ivan') newfunc2 = partial(greet, greeting='Hello', emphasis='.') newfunc2(name='German', separator='...') newfunc2(name='Ivan', separator='..')
      
      





別の部分的な例では、ループクロージャの問題を解決します。



 from functools import partial def makeActions(): acts = [] for i in range(5): def func(x, y): return x * y acts.append(partial(func, y=i)) # acts.append(partial(lambda x, y: x * y, y=i)) #  lambda # return [partial(lambda x, y: x * y, y=i) for i in range(5)] #    return acts for act in makeActions(): print(act(1), end=', ')
      
      





今日は以上です。 よろしくお願いします!



All Articles