Python'a yeni başladım ve hatırlamanın ne olduğu ve nasıl kullanılacağı hakkında hiçbir fikrim yok . Ayrıca, basitleştirilmiş bir örnek alabilir miyim?
Python'a yeni başladım ve hatırlamanın ne olduğu ve nasıl kullanılacağı hakkında hiçbir fikrim yok . Ayrıca, basitleştirilmiş bir örnek alabilir miyim?
Yanıtlar:
Memoization, yöntem girişlerine dayalı olarak yöntem çağrılarının sonuçlarının hatırlanmasını ("memoization" → "memorandum" → hatırlanması) ve daha sonra sonucu tekrar hesaplamak yerine hatırlanan sonucu döndürmeyi ifade eder. Bunu yöntem sonuçları için bir önbellek olarak düşünebilirsiniz. Daha fazla ayrıntı için, Algoritmalara Giriş (3e), Cormen ve ark.
Python'da not kullanarak faktöriyelleri hesaplamak için basit bir örnek şöyle olacaktır:
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]
Daha karmaşık hale gelebilir ve not verme sürecini bir sınıfa dahil edebilirsiniz:
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]
Sonra:
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
factorial = Memoize(factorial)
Python 2.4'te " dekoratörler " olarak bilinen bir özellik eklendi.
@Memoize
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
Python Dekoratör Kütüphanesi adlı benzer dekoratör sahiptir memoized
biraz daha sağlam daha Memoize
burada gösterilen sınıfın.
factorial_memo
, çünkü factorial
içeride def factorial
hala eski anımsama çağırıyor factorial
.
if k not in factorial_memo:
daha iyi okuyan da yazabilirsiniz if not k in factorial_memo:
.
args
bir demet. def some_function(*args)
argları bir demet yapar.
Python 3.2'de yeni functools.lru_cache
. Varsayılan olarak, yalnızca en son kullanılan 128 çağrıyı önbelleğe alır, ancak önbelleğin asla sona ermemesi gerektiğini belirtmek için maxsize
to None
değerini ayarlayabilirsiniz :
import functools
@functools.lru_cache(maxsize=None)
def fib(num):
if num < 2:
return num
else:
return fib(num-1) + fib(num-2)
Bu işlev tek başına çok yavaş, deneyin fib(36)
ve yaklaşık on saniye beklemek zorunda kalacaksınız.
lru_cache
Ek açıklama eklemek , işlev belirli bir değer için yakın zamanda çağrılmışsa, bu değeri yeniden hesaplamamasını, ancak önbelleğe alınmış önceki sonucu kullanmasını sağlar. Bu durumda, kod önbellek ayrıntılarıyla darmadağın olmamasına rağmen, muazzam bir hız iyileşmesine yol açar.
fib
çağrıldığında, hatırlama gerçekleşmeden önce temel duruma geri dönmesi gerekir. Yani, davranışınız hemen hemen beklenen.
Diğer cevaplar oldukça iyi olanı kapsar. Bunu tekrarlamıyorum. Sizin için yararlı olabilecek bazı noktalar.
Genellikle not, bir şeyi hesaplayan (pahalı) ve bir değer döndüren herhangi bir işleve uygulayabileceğiniz bir işlemdir. Bu nedenle, genellikle dekoratör olarak uygulanır . Uygulama basittir ve böyle bir şey olurdu
memoised_function = memoise(actual_function)
veya dekoratör olarak ifade edilir
@memoise
def actual_function(arg1, arg2):
#body
Memoization, pahalı hesaplamaların sonuçlarını tutar ve sürekli olarak yeniden hesaplamak yerine önbelleğe alınan sonucu döndürür.
İşte bir örnek:
def doSomeExpensiveCalculation(self, input):
if input not in self.cache:
<do expensive calculation>
self.cache[input] = result
return self.cache[input]
Daha eksiksiz bir açıklama , hafızaya alma hakkındaki wikipedia girişinde bulunabilir .
if input not in self.cache
ve self.cache[input]
( has_key
o zamandan beri kullanılmıyor ... 2.x serisinin başlarında, 2.0 değilse. self.cache(index)
Asla doğru değildi. IIRC)
hasattr
El işçiliği yapmak isteyenler için yerleşik işlevi unutmayalım . Bu şekilde mem önbelleğini işlev tanımının içinde tutabilirsiniz (genelin aksine).
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]
Bunu son derece kullanışlı buldum
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)
functools.wraps
.
memo
Belleğin boşaltılması için manuel olarak temizlemem gerekir mi?
Memoization temelde, daha sonraki bir aşamada aynı hesaplama gerekiyorsa özyineleme ağacında gezinme ihtiyacını azaltmak için özyinelemeli algoritmalarla yapılan geçmiş işlemlerin sonuçlarını kaydeder.
bkz. http://scriptbucket.wordpress.com/2012/12/11/introduction-to-memoization/
Python'daki Fibonacci Memoization örneği:
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]
Bellek, işlevlerin veri yapılarına dönüştürülmesidir. Genellikle dönüşümün aşamalı ve tembel bir şekilde gerçekleşmesi (belirli bir alan öğesinin veya "anahtarın" talebi üzerine) istenir. Tembel fonksiyonel dillerde, bu tembel dönüşüm otomatik olarak gerçekleşebilir ve böylece not verme (açık) yan etkiler olmadan uygulanabilir.
İlk olarak ilk bölümü cevaplamalıyım: Notlama nedir?
Bu sadece anı için ticaret yapmak için bir yöntem. Çarpım Tablosunu düşünün .
Değişken nesneyi Python'da varsayılan değer olarak kullanmak genellikle kötü kabul edilir. Ancak akıllıca kullanırsanız, a memoization
.
İşte http://docs.python.org/2/faq/design.html#why-are-default-values-shared-between-objects adresinden uyarlanmış bir örnek
dict
İşlev tanımında bir değişken kullanarak , ara hesaplanan sonuçlar önbelleğe alınabilir (örneğin, hesaplamadan factorial(10)
sonra hesaplama yaparken factorial(9)
, tüm ara sonuçları yeniden kullanabiliriz)
def factorial(n, _cache={1:1}):
try:
return _cache[n]
except IndexError:
_cache[n] = factorial(n-1)*n
return _cache[n]
İşte mızmız olmadan list veya dict tipi argümanlarla çalışacak bir çözüm:
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
Bu yaklaşımın, handle_item'de kendi karma işlevinizi özel bir durum olarak uygulayarak herhangi bir nesneye doğal olarak genişletilebileceğini unutmayın. Örneğin, bu yaklaşımın kümeyi girdi bağımsız değişkeni olarak alan bir işlev için çalışmasını sağlamak için handle_item öğesine ekleyebilirsiniz:
if is_instance(x, set):
return make_tuple(sorted(list(x)))
list
argümanı [1, 2, 3]
yanlışlıkla set
değeri olan farklı bir argümanla aynı kabul edilebilir {1, 2, 3}
. Ek olarak, setler sözlükler gibi sıralanmamıştır, bu yüzden de olması gerekir sorted()
. Ayrıca, özyinelemeli veri yapısı bağımsız değişkeninin sonsuz döngüye neden olacağını unutmayın.
list
s ve set
s'nin aynı şeye "ayrıştırılması" ve bu nedenle birbirinden ayırt edilemez hale gelmesinden kaynaklanıyor. En sets
son güncellemenizde açıklanan destek için örnek kod eklemek korkmamdan kaçınmıyor. Bu ayrı ayrı geçerek [1,2,3]
ve {1,2,3}
bir "memoize" d test fonksiyonuna bir argüman olarak ve olması gerektiği gibi iki kez çağrılıp çağrılmadığını görerek kolayca görülebilir .
list
s ve dict
bunun nedeni s olası bir için list
çağrıda kaynaklanan içinde tam olarak aynı şeyi yapmak make_tuple(sorted(x.items()))
bir sözlük için. Her iki durum için de basit bir çözüm, type()
oluşturulan demet içerisindeki değerin dahil edilmesi olacaktır . Özellikle set
s'yi ele almanın daha basit bir yolunu düşünebilirim , ancak genelleme yapmaz.
Anahtar kelime bağımsız değişkenlerinin geçirildiği sıradan bağımsız olarak hem konumsal hem de anahtar kelime bağımsız değişkenleriyle çalışan çözüm ( inspect.getargspec kullanılarak ):
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
Benzer soru: Python'da eşdeğer varargs işlevinin tanımlanması
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]
if n not in cache
bunun yerine sadece kullanabilirsiniz . kullanarak cache.keys
python 2'de gereksiz bir liste oluşturacaksınız
Sadece verilen cevaplara eklemek istedim, Python dekoratör kütüphanesinin aksine "paylaşılamaz türler" i hatırlayabilen bazı basit ama kullanışlı uygulamaları var functools.lru_cache
.