2017-04-03 3 views
3

Ich habe ein folgenden Anwendungsfall:Capturing Verfahren in if-Anweisung in einer Liste Verständnis

[(x, f(x)) for x in list_x if f(x) == cond(x)] 

Above Liste Verständnis, ich denke, macht ein f (x) zweimal anrufen? Wie vermeide ich das und fange Wert von f (x), so dass f (x) nur einmal aufgerufen wird.

Ich denke, einfache Lösung ist, über Listenverständnis in eine for-Schleife zu konvertieren, aber bin neugierig, wenn es effizient mit einem Listenverständnis getan werden kann.

Antwort

6

Sie einen Ausdruck verwenden können verschachtelte Generator anwenden kann nur einmal um die Funktion aufzurufen:

[(x, fx) for (x, fx) in ((x, f(x)) for x in list_x) if fx == cond(x)] 

Der Generatorausdruck wird im Lockstep iteriert, um die (x, fx) Tupel für das Listenverständnis zu erzeugen.

Wenn Sie diese leichter auf den Leser zu finden, können Sie zuerst den Generator Ausdruck in einen separaten Namen aufzuschlüsseln:

mapped_x = ((x, f(x)) for x in list_x) 
filtered_x = [(x, fx) for (x, fx) in mapped_x if fx == cond(x)] 

Um diesen Punkt erneut durchlaufen: der Generator Ausdruck wird träge ausgeführt; Die Schleife for im Ausdruck wird Schritt für Schritt für jeden Schritt in der Schleife for ... in mapped_x vorgerückt.

Demo:

>>> list_x = range(5) 
>>> f = lambda x: print('f({!r})'.format(x)) or (x ** 2 - 1) 
>>> cond = lambda x: print('cond({!r})'.format(x)) or x % 2 == 0 
>>> mapped_x = ((x, f(x)) for x in list_x) 
>>> [(x, fx) for (x, fx) in mapped_x if fx == cond(x)] 
f(0) 
cond(0) 
f(1) 
cond(1) 
f(2) 
cond(2) 
f(3) 
cond(3) 
f(4) 
cond(4) 
[(1, 0)] 

Beachten Sie, wie f(x) nur einmal aufgerufen wird, und der Zustand sofort überprüft wird.

Wie effizient das ist, hängt davon ab, wie teuer der f(x) Anruf ist; Ein Generatorausdruck wird als separater Funktionsrahmen ausgeführt, und der Interpreter wechselt zwischen den beiden Rahmen (die Schleife für das Listenverständnis ist ebenfalls ein Rahmenobjekt). Wenn f(x) eine Python-Funktion ist, haben Sie bereits gewonnen, da Sie nun die Anzahl der erstellten Funktionsrahmenobjekte halbiert haben (jeder Aufruf von f(x) erstellt auch ein Rahmenobjekt und das Erstellen dieser Rahmen ist relativ kostspielig). Für C-Funktionen sollten Sie einige Testläufe mit der timeit module erstellen, um zu sehen, was für Ihre erwartete Listengröße schneller ist.

1

Wenn Sie f zweimal nicht nennen wollen, Sie f in die Liste zuerst

[a for a in map(lambda x: (x, f(x)), list_x) if a[1] == cond(x)]

+0

wenn Sie 'verwenden map' warum nicht' filter' auch mit? ... – Julien

+0

Sie sind willkommen Ihre Version von Antwort –

+0

Hinweis zu liefern, dass eine 'lambda' mit diesem deutlich verlangsamt (Erstellen und knallend das Funktionsrahmenobjekt N mal für eine Listenlänge N). –

1

Filter wäre eine saubere Möglichkeit, dies zu tun. Es wird die übergebene Funktion auswerten und wenn es wahr zurückgibt, behält es das Element, andernfalls nein.

print (list(filter(lambda x: x[1] == cond(x[0]), [(x, f(x)) for x in list_x]))) 
Verwandte Themen