2012-01-06 9 views
7

Voglio costruire un valore in una lista di comprensione, ma anche filtrare su quel valore. Per esempio:È possibile acquisire il valore restituito da una comprensione elenco Python per utilizzare una condizione?

[expensive_function(x) for x in generator where expensive_function(x) < 5] 

voglio evitare di chiamare expensive_function due volte per ogni iterazione.

Il generator può restituire una serie infinita e la comprensione delle liste non viene ponderata. Quindi questo non avrebbe funzionato:

[y in [expensive_function(x) for x in generator where expensive_function(x)] where y < 5] 

ho potuto scrivere questo in un altro modo, ma ci si sente giusto per una lista di comprensione e sono sicuro che questo è un modello di utilizzo comune (possibile o no!).

+0

Qualcuna delle risposte è degna di essere accettata? In caso contrario, quali informazioni stai ancora cercando? –

+0

Mi dispiace dimenticarmi di segnare questo. La ringrazio per la risposta! – Joe

+0

Nessun problema. Speravo che avessi solo bisogno di un promemoria. :) –

risposta

10

Se generator può essere infinito, non si desidera utilizzare una comprensione di lista. E non tutto deve essere un solitario.

def filtered_gen(gen): 
    for item in gen: 
     result = expensive_function(item) 
     if result < 5: 
      yield result 
+2

Le ultime due occorrenze di 'item' devono essere sostituite da' result'? – Chris

+0

@Chris: Sì, grazie. –

+0

+1 a questo. Puoi usare itertools e le espressioni del generatore, ma questo è molto più facile da capire. –

2

si dovrebbe fare espressioni 2 generatori:

ys_all = (expensive(x) for x in xs) 
ys_filtered = (y for y in ys_all if y <5) 

o

from itertools import imap, ifilter 
ys = ifilter(lambda y : y < 5, imap(expensive, xs)) 
+0

No. Non se 'xs' è infinito. Tristemente Python non ha la comprensione delle liste Haskell. Qualcosa, da qualche parte, esploderebbe. – Joe

+0

risposta aggiornata con iteratori – Simon

+0

Sì! Questa è la migliore risposta a mio parere! Potresti forse chiamarli "generatori di comprensione" invece di comprendere le liste? –

1

Attenzione Questo è un po 'contorto, ma non il lavoro. Userò un esempio per spiegarlo.

diciamo expensive_function = math.sin

infinite generator = collections.count(0.1,0.1)

poi

[z for z in (y if y < 5 else next(iter([])) 
    for y in (math.sin(x) for x in itertools.count(0.1,0.1)))] 

è

[0.09983341664682815, 
0.19866933079506122, 
0.2955202066613396, 
0.3894183423086505, 
0.479425538604203] 

Quindi il problema si riduce a

[z for z in (y if y < 0.5 else next(iter([])) \ 
     for y in (expensive_function(x) for x in generator))] 

Il trucco è quello di forzare un StopIteration da un generatore e niente elegante rispetto next(iter([]))

Qui expensive_function viene chiamato solo una volta per ogni iterazione.

Estendere il generatore infinito con un generatore finito, con la condizione di arresto. Dato che il generatore non consente raise StopIteration, optiamo per un modo convoluto, ad es.next(iter([])) E ora avete un generatore finiti, che può essere utilizzato in una list comprehension

Come OP riguardava l'applicazione del metodo di cui sopra per un non monotonic funzione: ecco un fittizio funzione non-monotona

costoso non monotona Funzione f(x) = random.randint(1,100)*x

arresto Condizione = < 7

[z for z in (y if y < 7 else next(iter([])) for y in 
     (random.randint(1,10)*x for x in itertools.count(0.1,0.1)))] 

[0.9, 
0.6000000000000001, 
1.8000000000000003, 
4.0, 
0.5, 
6.0, 
4.8999999999999995, 
3.1999999999999997, 
3.5999999999999996, 
5.999999999999999] 

Btw: sin nel vero senso non è monotonico su tutto il range (0,2pi)

+0

Pazzo! Questo è sottilmente differente, perché 'sin' è una funzione non monotona (non ho detto che' costosi_funzione' è monotona, ma lo è!) E questo si ferma ** la prima volta ** la condizione non è vera, non continua ** per tutte le condizioni ** in cui la condizione è vera. Detto questo, se fosse non monotono, questo risulterebbe in una valutazione infinita ... – Joe

+0

@Joe, Funzionerà anche per la funzione non monotona. Vedi il mio aggiornamento – Abhijit

2

Ho intenzione di rispondere alla parte della domanda su come acquisire risultati intermedi in un elenco di comprensione per l'uso in una condizione e ignorare la domanda di un lista di comprensione costruita da un generatore infinito (che ovviamente non funzionerà), nel caso in cui qualcuno cerchi una risposta alla domanda nel titolo venga qui.

Quindi, avete una lista di comprensione come questo:

[expensive_function(x) for x in xrange(5) if expensive_function(x) % 2 == 0] 

E si vuole evitare il calcolo expensive_function due volte quando si passa il filtro. Lingue con la sintassi di comprensione più espressivo (Scala, Haskell, ecc) consentono di assegnare semplicemente i nomi alle espressioni calcolate dalle variabili di comprensione, che ti permette di fare cose come la seguente:

# NOT REAL PYTHON 
[result for x in xrange(5) for result = expensive_function(x) if result % 2 == 0] 

ma si può facilmente emulare questo girando l'assegnazione result = expensive_function(x) in un'altra for iterazione su una sequenza di un elemento:

[result for x in xrange(5) for result in (expensive_function(x),) if result % 2 == 0] 

E la prova:

>>> def expensive_function(x): 
     print 'expensive_function({})'.format(x) 
     return x + 10 
>>> [expensive_function(x) for x in xrange(5) if expensive_function(x) % 2 == 0] 
expensive_function(0) 
expensive_function(0) 
expensive_function(1) 
expensive_function(2) 
expensive_function(2) 
expensive_function(3) 
expensive_function(4) 
expensive_function(4) 
[10, 12, 14] 
>>> [result for x in xrange(5) for result in (expensive_function(x),) if result % 2 == 0] 
expensive_function(0) 
expensive_function(1) 
expensive_function(2) 
expensive_function(3) 
expensive_function(4) 
[10, 12, 14] 
Problemi correlati