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 memoizedque é um pouco mais robusto do que a Memoizeclasse mostrada aqui.
factorial_memo, porque o factorialinterior def factorialainda 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 Nonepara 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_cacheanotaçã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.
fibfor 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 hasattrfunçã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.
memopara 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 dictna 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)))
listargumento de [1, 2, 3]pode erroneamente ser considerado o mesmo que um setargumento 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.
listes setsão "tuplaizados" na mesma coisa e, portanto, tornam-se indistinguíveis um do outro. O código de exemplo para adicionar suporte setsdescrito 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.
lists e dicts porque é possível que lista 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 sets, 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 cachevez disso. usando cache.keyscriaria 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.