2010-07-14 10 views
0

Voici un extrait de code qui montre le code que je voudrais optimiser:Optimiser une compréhension de la liste

result = [(item, foo(item)) 
      for item in item_list 
      if cond1(item) and cond2(foo(item))] 

Dans l'extrait ci-dessus que j'appelle foo(item) deux fois. Je ne peux pas penser à un moyen d'itérer sur la liste seulement une fois maintenir à la fois item et foo(item) pour le conditionnel et la liste de résultats.

C'est, je voudrais garder item et foo(item) sans avoir à faire deux fois la boucle sur la liste et sans avoir à appeler foo(item).

Je sais que je peux le faire avec une deuxième compréhension de la liste imbriquée:

result = [(item, foo_item) 
      for item, foo_item in [(i, foo(i)) for i in item_list] 
      if cond1(item) and cond2(foo_item)] 

mais qui semble boucle à travers item_list deux fois que je voudrais éviter.

Ainsi, le premier exemple appelle foo deux fois par élément de liste. Le deuxième exemple parcourt deux fois la liste (ou apparaît). Je voudrais boucler une fois et appeler le foo une fois pour chaque article.

+0

Ce serait génial si vous pouviez tester la performance des différentes réponses et afficher les résultats ici. C'est un peu difficile pour quelqu'un d'autre de le faire sans connaître le temps de fonctionnement de 'cond1',' cond2' et 'foo' –

Répondre

4

Il n'a pas, mais ici:

result = [(item, foo_item) 
    for item, foo_item in ((i, foo(i)) for i in item_list) 
    if cond1(item) and cond2(foo_item)] 

Tourner la compréhension de la liste intérieure dans une expression génératrice fait en sorte que nous ne l'utilisons pas une liste temporaire inutile.

+0

Cela est identique à mon deuxième exemple sauf que vous avez changé ma compréhension de liste en une expression de générateur. Êtes-vous en train de dire que mon deuxième exemple ne ferait que parcourir la liste une fois? Ou êtes-vous en train de dire que pour qu'il ne boucle que lorsque j'ai besoin d'une expression de générateur? –

+0

Une liste de compréhension crée une liste. Donc, vous itérez sur 'item_list', en appliquant la fonction à chaque élément et en retournant les résultats sous forme de liste. Vous itérez alors sur * cette * liste, en faisant apparaître comme si vous étiez en train d'itéter deux fois sur item_list, alors qu'en fait c'est juste un effet secondaire de l'algorithme. –

+0

L'exemple avec liste interne parcourra toute la liste_élément pour créer une liste temporaire, puis parcourir cette liste. L'exemple avec générateur va itérer par item_list et pour chaque élément, exécuter le reste du code - exactement ce que vous avez demandé. – zvone

4

Comme je l'ai été dit à plusieurs reprises ici, la meilleure chose dans ce cas est de ne pas utiliser une compréhension de la liste à tous:

result = [] 
for item in item_list: 
    if cond1(item): 
     value = foo(item) 
     if cond2(value): 
      result.append((item, value)) 

Mais je suis stubbborn, donc nous allons voir ce que je peux venir avec (et gardez la compréhension) (oh, attendez - j'ai tout votre code erroné.Détaillant - et déplaçant des variables intermédiaires est la manière directe de ne pas répéter l'appel)

+0

Je suis pareil. Évidemment, il peut être écrit efficacement de manière impérative, mais je suis intéressé par une solution fonctionnelle ou basée sur la compréhension d'une liste. –

+2

+1 Ceci est simple, lisible et susceptible d'être le plus rapide. Quelqu'un veut-il faire des benchmarks? –

3

Comment cela ressemble-t-il?

result = [ (i, fi) for i in item_list if cond1(i) 
        for fi in (foo(i),) if cond2(fi) ] 
+0

C'est assez joli en fait. Je me demande si l'emballage et le déballage de 'foo (i)' dans une liste sont coûteux. Pourtant, c'est une façon propre de résoudre le problème. Je vais y réfléchir. –

+0

J'espère que la création d'une liste (ou d'un tuple mono-élément) utilise un chemin de code hautement optimisé dans l'interpréteur Python. Il doit être plus rapide que la création et l'itération à travers un générateur. Je me réjouis des commentaires de quelqu'un qui a réellement évalué ces suggestions. – Karmastan

+0

Bon - Je venais maintenant de réessayer d'assigner foo [i] à quelque chose (comme un locals() .__ setitem__ call) mais cet item est beaucoup plus propre. (encore une fois, juste pour le plaisir de le faire en une ligne) – jsbueno

3

Utiliser des expressions de générateur.

result = [(item, foo_item) 
      for item, foo_item in ((i, foo(i)) for i in item_list) 
      if cond1(item) and cond2(foo_item)] 

L'interprète passera par chaque élément exactement une fois, parce que l'expression du générateur calcule (i, foo(i)) seulement quand il est requis par la boucle extérieure.

En supposant que foo est cher et n'a pas d'effets secondaires, je dirais même essayer de le faire:

result = [(item, foo_item) 
      for item, foo_item in ((i, foo(i)) for i in item_list if cond1(i)) 
      if cond2(foo_item)] 

de sorte que foo ne seront pas appelés pour les éléments qui ne passent pas la première condition. En fait, cela semble mieux pour moi quand il est écrit fonctionnellement:

from itertools import imap, ifilter 
result = filter((lambda i,f:cond2(f)), 
      imap((lambda i:(i, foo(i))), 
      ifilter(cond1, item_list))) 

... mais je pourrais être subjective.

+0

Il serait naturel de déplacer le conditionnel dans l'expression du générateur pour le réduire. –

1

Ceci est l'une des nombreuses raisons pour lesquelles nous avons des générateurs:

def generator(items): 
    for item in items: 
     if cond1(item): 
      food = foo(item) 
      if food: 
       yield item, food 

result = list(generator(item_list)) 

LCs ne sont bons quand ils regardent bien - si vous devez les étaler sur 3 lignes juste pour les rendre lisibles, il est une mauvaise idée .