62 votes

Compréhension de la liste Python - veut éviter une évaluation répétée

J'ai une compréhension de liste qui se rapproche à:

[f(x) for x in l if f(x)]

Où l est une liste, et f(x) est une fonction coûteuse qui renvoie une liste.

Je veux éviter de l'évaluation de f(x) deux fois pour chaque non-vide occurrence de f(x). Est-il un moyen de sauver sa sortie dans la compréhension de liste?

J'ai pu supprimer la dernière condition, de générer l'ensemble de la liste, puis le tailler, mais cela semble inutile.

Edit:

Deux approches ont été proposées:

Intérieure d'un générateur de compréhension:

[y for y in (f(x) for x in l) if y]

ou memoization.

Je pense que l'intérieur du générateur de compréhension est à la fois élégante pour le problème comme indiqué. En fait, j'ai simplifié la question de la rendre claire, je veux vraiment:

[g(x, f(x)) for x in l if f(x)]

Pour cette situation plus compliquée, je pense que memoization produit un nettoyant pour le résultat final.

51voto

RobertT Points 751

[y for y in (f(x) for x in l) if y] fera

11voto

EnricoGiampieri Points 2065

Une solution (le meilleur si vous avez répété la valeur de x) serait à memoize la fonction f, c'est à dire de créer une fonction wrapper qui permet d'économiser l'argument par lequel la fonction est appelée et de l'enregistrer, de le retourner si la même valeur est demandé.

l'une, très simple de mise en œuvre est la suivante:

storage = {}
def memoized(value):
    if value not in storage:
        storage[value] = f(value)
    return storage[value]

[memoized(x) for x in l if memoized(x)]

et ensuite utiliser cette fonction dans la compréhension de liste. Cette approche est valable que sous deux conditions, l'une théorique et une pratique. La première est que la fonction f doit être déterministe, c'est à dire renvoie les mêmes résultats à la lumière de la même entrée, et l'autre est que l'objet x peut être utilisé comme un dictionnaire clés. Si le premier n'est pas valide, vous devez recalculer f chaque timeby définition, tandis que si le second échoue, il est possible d'utiliser certains un peu plus robuste approches.

Vous pouvez trouver beaucoup de mise en œuvre de memoization sur le net, et je pense que les nouvelles versions de python ont quelque chose inclus dans eux trop.

Sur une note de côté, n'utilisez jamais le petit L comme un nom de variable, est une mauvaise habitude, comme elle peut être confondue avec un i ou un 1 sur certains terminaux.

EDIT:

comme indiqué, une solution possible à l'aide de générateurs de compréhension (pour éviter de créer inutile de dupliquer temporaires) serait de cette expression:

[g(x, fx) for x, fx in ((x,f(x)) for x in l) if fx]

Vous avez besoin de poids de votre choix compte tenu du coût de calcul de f, le nombre de doublons dans la liste d'origine et de la mémoire à vous disposition. Memoization faire un espace-vitesse, ce qui signifie qu'il garder des morceaux de chaque résultat de l'enregistrer, donc si vous avez des grandes listes elle peut devenait coûteux sur la mémoire de l'occupation avant.

10voto

Inbar Rose Points 13033

Vous devriez utiliser un décorateur memoize. Voici un lien intéressant.


Utilisation de la mémorisation à partir du lien et de votre "code":

 def memoize(f):
    """ Memoization decorator for functions taking one or more arguments. """
    class memodict(dict):
        def __init__(self, f):
            self.f = f
        def __call__(self, *args):
            return self[args]
        def __missing__(self, key):
            ret = self[key] = self.f(*key)
            return ret
    return memodict(f)

@memoize
def f(x):
    # your code

[f(x) for x in l if f(x)]
 

9voto

Vaughn Cato Points 30511
 [y for y in [f(x) for x in l] if y]
 

Pour votre problème mis à jour, cela pourrait être utile:

 [g(x,y) for x in l for y in [f(x)] if y]
 

8voto

alexis Points 10856

Comme les réponses précédentes l'ont montré, vous pouvez utiliser un double de la compréhension ou de l'utilisation memoization. De taille raisonnable, des problèmes, c'est une question de goût (et je suis d'accord que memoization semble plus propre, car il cache l'optimisation). Mais si vous êtes de l'examen d'une liste très importante, il y a une énorme différence: Memoization va stocker chaque valeur unique que vous avez calculé, et on peut vite exploser votre mémoire. Une double compréhension avec un générateur (ronde des parens, pas de crochets) stocke uniquement ce que vous souhaitez conserver.

Pour venir à votre problème:

[g(x, f(x)) for x in series if f(x)]

Pour calculer la valeur finale, vous avez besoin d' x et f(x). Pas de problème, passez-les à la fois comme ceci:

[g(x, y) for (x, y) in ( (x, f(x)) for x in series ) if y ]

Encore une fois: ce devrait être l'aide d'un générateur (rond parens), pas une compréhension de liste (entre crochets). Sinon, vous permettra de construire l'ensemble de la liste avant de commencer à filtrer les résultats. C'est la compréhension de liste version:

[g(x, y) for (x, y) in [ (x, f(x)) for x in series ] if y ] # DO NOT USE THIS

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X