Acabei de iniciar o Python e não tenho ideia do que é memorização e como usá-la. Além disso, posso ter um exemplo simplificado?
Acabei de iniciar o Python e não tenho ideia do que é memorização e como usá-la. Além disso, posso ter um exemplo simplificado?
Respostas:
Memorização refere-se efetivamente aos resultados de memorização ("memorização" → "memorando" → a ser lembrado) de chamadas de método com base nas entradas do método e, em seguida, retornando o resultado lembrado em vez de computá-lo novamente. Você pode pensar nisso como um cache para obter resultados do método. Para mais detalhes, consulte a página 387 para a definição em Introdução aos algoritmos (3e), Cormen et al.
Um exemplo simples para calcular fatoriais usando memoização em Python seria algo como isto:
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]
Você pode ficar mais complicado e encapsular o processo de memorização em uma classe:
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]
Então:
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
factorial = Memoize(factorial)
Um recurso conhecido como " decoradores " foi adicionado no Python 2.4, que permite que você simplesmente escreva o seguinte para realizar a mesma coisa:
@Memoize
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
A Python Decorator Library possui um decorador semelhante chamado memoized
que é um pouco mais robusto do que a Memoize
classe mostrada aqui.
factorial_memo
, porque o factorial
interior def factorial
ainda chama o antigo desmembrar factorial
.
if k not in factorial_memo:
, que lê melhor que if not k in factorial_memo:
.
args
é uma tupla. def some_function(*args)
faz args uma tupla.
Novo no Python 3.2 é functools.lru_cache
. Por padrão, ele armazena em cache apenas as 128 chamadas usadas mais recentemente, mas você pode configurá maxsize
-lo None
para indicar que o cache nunca deve expirar:
import functools
@functools.lru_cache(maxsize=None)
def fib(num):
if num < 2:
return num
else:
return fib(num-1) + fib(num-2)
Esta função por si só é muito lenta, tente fib(36)
e você terá que esperar cerca de dez segundos.
A adição de lru_cache
anotação garante que, se a função tiver sido chamada recentemente para um valor específico, ela não recalculará esse valor, mas usará um resultado anterior em cache. Nesse caso, isso leva a uma tremenda melhoria de velocidade, enquanto o código não é confuso com os detalhes do cache.
fib
for chamado, será necessário retornar ao caso base antes que a memorização possa acontecer. Portanto, seu comportamento é praticamente o esperado.
As outras respostas cobrem o que está muito bem. Eu não estou repetindo isso. Apenas alguns pontos que podem ser úteis para você.
Normalmente, a memória é uma operação que você pode aplicar em qualquer função que calcule algo (caro) e retorne um valor. Por esse motivo, é frequentemente implementado como um decorador . A implementação é simples e seria algo como isto
memoised_function = memoise(actual_function)
ou expresso como um decorador
@memoise
def actual_function(arg1, arg2):
#body
A memorização é manter os resultados de cálculos caros e retornar o resultado em cache, em vez de recalculá-lo continuamente.
Aqui está um exemplo:
def doSomeExpensiveCalculation(self, input):
if input not in self.cache:
<do expensive calculation>
self.cache[input] = result
return self.cache[input]
Uma descrição mais completa pode ser encontrada na entrada da Wikipedia sobre memorização .
if input not in self.cache
e self.cache[input]
( has_key
é obsoleta desde ... no início da série 2.x, se não for 2.0. self.cache(index)
Nunca foi correta IIRC.)
Não vamos esquecer a hasattr
função embutida, para quem deseja criar manualmente. Dessa forma, você pode manter o cache de mem dentro da definição da função (em oposição a um global).
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]
Eu achei isso extremamente útil
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
para que a memória seja liberada?
A memorização consiste basicamente em salvar os resultados de operações anteriores feitas com algoritmos recursivos, a fim de reduzir a necessidade de percorrer a árvore de recursão se o mesmo cálculo for necessário posteriormente.
consulte http://scriptbucket.wordpress.com/2012/12/11/introduction-to-memoization/
Exemplo de memorização de Fibonacci em 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]
Memoização é a conversão de funções em estruturas de dados. Normalmente, a conversão é realizada de forma incremental e lenta (sob demanda de um determinado elemento de domínio - ou "chave"). Em linguagens funcionais preguiçosas, essa conversão preguiçosa pode ocorrer automaticamente e, portanto, a memorização pode ser implementada sem efeitos colaterais (explícitos).
Bem, eu deveria responder a primeira parte primeiro: o que é memorização?
É apenas um método para trocar memória por tempo. Pense na tabela de multiplicação .
Usar objeto mutável como valor padrão no Python geralmente é considerado ruim. Mas se usá-lo com sabedoria, pode ser realmente útil implementar a memoization
.
Aqui está um exemplo adaptado de http://docs.python.org/2/faq/design.html#why-are-default-values-shared-between-objects
Usando um mutável dict
na definição da função, os resultados intermediários calculados podem ser armazenados em cache (por exemplo, ao calcular factorial(10)
após o cálculo factorial(9)
, podemos reutilizar todos os resultados intermediários)
def factorial(n, _cache={1:1}):
try:
return _cache[n]
except IndexError:
_cache[n] = factorial(n-1)*n
return _cache[n]
Aqui está uma solução que funcionará com argumentos de lista ou tipo de ditado sem reclamar:
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
Observe que essa abordagem pode ser estendida naturalmente a qualquer objeto, implementando sua própria função de hash como um caso especial no handle_item. Por exemplo, para fazer essa abordagem funcionar para uma função que aceita um conjunto como argumento de entrada, você pode adicionar ao handle_item:
if is_instance(x, set):
return make_tuple(sorted(list(x)))
list
argumento de [1, 2, 3]
pode erroneamente ser considerado o mesmo que um set
argumento diferente com um valor de {1, 2, 3}
. Além disso, os conjuntos não são ordenados como dicionários, portanto, também precisam ser sorted()
. Observe também que um argumento recursivo da estrutura de dados causaria um loop infinito.
list
es set
são "tuplaizados" na mesma coisa e, portanto, tornam-se indistinguíveis um do outro. O código de exemplo para adicionar suporte sets
descrito em sua atualização mais recente não evita que eu tenha medo. Isso pode ser facilmente visto passando separadamente [1,2,3]
e {1,2,3}
como argumento para uma função de teste "memoize" d e ver se é chamado duas vezes, como deveria ser ou não.
list
s e dict
s porque é possível que list
a tenha exatamente a mesma coisa que resultou da chamada make_tuple(sorted(x.items()))
de um dicionário. Uma solução simples para os dois casos seria incluir o type()
valor of na tupla gerada. Posso pensar em uma maneira ainda mais simples de lidar especificamente com set
s, mas isso não generaliza.
Solução que funciona com argumentos posicionais e de palavras-chave, independentemente da ordem em que os argumentos das palavras-chave foram transmitidos (usando 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
Pergunta semelhante: Identificar funções varargs equivalentes exige memorização em 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]
if n not in cache
vez disso. usando cache.keys
criaria uma lista desnecessária em python 2
Só queria acrescentar às respostas já fornecidas, a biblioteca decoradora Python tem algumas implementações simples, mas úteis, que também podem memorizar "tipos laváveis", ao contrário functools.lru_cache
.