Je viens juste de commencer Python et je n'ai aucune idée de ce que mémorisation et comment l'utiliser. Aussi, puis-je avoir un exemple simplifié?
La mémorisation se réfère effectivement à la mémorisation ("mémorisation" → "mémorandum" → à retenir) des résultats d'appels de méthode basés sur les entrées de la méthode, puis au résultat mémorisé plutôt que de le calculer à nouveau. Vous pouvez le considérer comme un cache pour les résultats de la méthode. Pour plus de détails, voir page 387 pour la définition dans Introduction aux algorithmes (3e), Cormen et Al.
Un exemple simple de calcul de factorielles utilisant la mémorisation dans Python ressemblerait à ceci:
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]
Vous pouvez devenir plus compliqué et encapsuler le processus de mémorisation dans une 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]
Ensuite:
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
factorial = Memoize(factorial)
Une fonctionnalité appelée " décorateurs " a été ajoutée dans Python 2.4, ce qui vous permet d'écrire simplement ce qui suit pour accomplir la même chose:
@Memoize
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
La bibliothèque de décorateurs Python possède un décorateur similaire appelé memoized
qui est légèrement plus robuste que la classe Memoize
présentée ici.
La nouveauté de Python 3.2 est functools.lru_cache
. Par défaut, seuls les 128 derniers appels utilisés sont mis en cache, mais vous pouvez définir le paramètre maxsize
sur None
pour indiquer que le cache ne doit jamais expirer:
_import functools
@functools.lru_cache(maxsize=None)
def fib(num):
if num < 2:
return num
else:
return fib(num-1) + fib(num-2)
_
Cette fonction en soi est très lente, essayez fib(36)
et vous devrez attendre environ dix secondes.
L'ajout de l'annotation _lru_cache
_ permet de s'assurer que si la fonction a été appelée récemment pour une valeur particulière, elle ne recalculera pas cette valeur, mais utilisera un résultat précédent mis en cache. Dans ce cas, cela entraîne une amélioration considérable de la vitesse, alors que le code n’est pas encombré par les détails de la mise en cache.
Les autres réponses couvrent ce que c'est très bien. Je ne répète pas ça. Juste quelques points qui pourraient vous être utiles.
En général, la mémorisation est une opération que vous pouvez appliquer à toute fonction qui calcule quelque chose (cher) et renvoie une valeur. Pour cette raison, il est souvent implémenté en tant que décorateur . La mise en œuvre est simple et ce serait quelque chose comme ça
memoised_function = memoise(actual_function)
ou exprimé en tant que décorateur
@memoise
def actual_function(arg1, arg2):
#body
La mémorisation consiste à conserver les résultats de calculs coûteux et à renvoyer le résultat mis en cache plutôt que de le recalculer en permanence.
Voici un exemple:
def doSomeExpensiveCalculation(self, input):
if input not in self.cache:
<do expensive calculation>
self.cache[input] = result
return self.cache[input]
Une description plus complète peut être trouvée dans le entrée de Wikipédia sur la mémoisation .
N'oublions pas la fonction intégrée hasattr
, pour ceux qui souhaitent créer à la main. De cette façon, vous pouvez conserver le cache de mémoire dans la définition de la fonction (par opposition à une définition globale).
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]
J'ai trouvé cela extrêmement utile
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)
La mémorisation consiste essentiellement à enregistrer les résultats des opérations passées effectuées avec des algorithmes récursifs afin de réduire le besoin de traverser l'arbre de récurrence si le même calcul est requis à un stade ultérieur.
voir http://scriptbucket.wordpress.com/2012/12/11/introduction-to-memoization/
Exemple de mémorisation Fibonacci en 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]
Eh bien, je devrais d'abord répondre à la première partie: qu'est-ce que la mémoisation?
C'est juste une méthode pour échanger de la mémoire contre du temps. Pensez à Table de multiplication .
L'utilisation d'un objet mutable comme valeur par défaut dans Python est généralement considérée comme incorrecte. Mais si vous l'utilisez judicieusement, il peut être utile d'implémenter un memoization
.
Voici un exemple adapté de http://docs.python.org/2/faq/design.html#why-are-default-values-shared-between-objects
En utilisant un mutable dict
dans la définition de la fonction, les résultats calculés intermédiaires peuvent être mis en cache (par exemple, lors du calcul de factorial(10)
après le calcul factorial(9)
, nous pouvons réutiliser tous les résultats intermédiaires)
def factorial(n, _cache={1:1}):
try:
return _cache[n]
except IndexError:
_cache[n] = factorial(n-1)*n
return _cache[n]
La mémorisation est la conversion de fonctions en structures de données. Habituellement, on souhaite que la conversion se fasse de manière incrémentielle et paresseuse (à la demande d'un élément de domaine donné - ou "clé"). Dans les langages fonctionnels paresseux, cette conversion paresseuse peut s'effectuer automatiquement et, par conséquent, la mémorisation peut être implémentée sans effets secondaires (explicites).
Voici une solution qui fonctionnera avec des arguments de type liste ou dict sans se plaindre:
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
Notez que cette approche peut naturellement être étendue à n’importe quel objet en implémentant votre propre fonction de hachage en tant que cas particulier dans handle_item. Par exemple, pour que cette approche fonctionne avec une fonction prenant un ensemble en tant qu'argument d'entrée, vous pouvez ajouter à handle_item:
if is_instance(x, set):
return make_Tuple(sorted(list(x)))
Solution fonctionnant avec des arguments de position et de mot clé indépendamment de l'ordre dans lequel les arguments de mot clé ont été transmis (à l'aide de 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
Question similaire: Identifier les fonctions équivalentes de varargs appelle une mémoisation en Python
Je voulais juste ajouter aux réponses déjà fournies, la bibliothèque de décorateurs Python possède des implémentations simples mais utiles qui peuvent également mémoriser des "types non contraignants", à la différence de functools.lru_cache
.
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]