Come filtrare in modo efficiente i valori calcolati all'interno di una comprensione dell'elenco Python?

StackOverflow https://stackoverflow.com/questions/130262

  •  02-07-2019
  •  | 
  •  

Domanda

La sintassi di comprensione dell'elenco Python semplifica il filtraggio dei valori all'interno di una comprensione. Ad esempio:

result = [x**2 for x in mylist if type(x) is int]

Restituirà un elenco dei quadrati di numeri interi nella mia lista. Tuttavia, cosa succede se il test prevede un calcolo (costoso) e si desidera filtrare il risultato? Un'opzione è:

result = [expensive(x) for x in mylist if expensive(x)]

Ciò comporterà un elenco di non "falso" valori costosi (x), tuttavia costoso () viene chiamato due volte per ogni x. Esiste una sintassi di comprensione che ti consente di eseguire questo test chiamando solo costoso una volta per x?

È stato utile?

Soluzione

Se i calcoli sono già ben raggruppati in funzioni, che ne dici di usare filter e map ?

result = filter (None, map (expensive, mylist))

Puoi usare itertools.imap se l'elenco è molto grande.

Altri suggerimenti

È venuto con la mia risposta dopo un minuto di riflessione. Può essere fatto con comprensioni nidificate:

result = [y for y in (expensive(x) for x in mylist) if y]

Immagino che funzioni, anche se trovo che le comprensioni nidificate siano leggibili solo marginalmente

La risposta più ovvia (e direi più leggibile) è di non usare una comprensione dell'elenco o un'espressione del generatore, ma piuttosto un vero generatore:

def gen_expensive(mylist):
    for item in mylist:
        result = expensive(item)
        if result:
            yield result

Richiede più spazio orizzontale, ma è molto più facile vedere cosa fa a colpo d'occhio e alla fine non ti ripeti.

result = [x for x in map(expensive,mylist) if x]

map () restituirà un elenco dei valori di ciascun oggetto nella mia lista passati a costoso (). Quindi puoi comprenderlo e scartare i valori non necessari.

È un po 'come una comprensione annidata, ma dovrebbe essere più veloce (poiché l'interprete di Python può ottimizzarlo abbastanza facilmente).

Questo è esattamente ciò che i generatori sono adatti a gestire:

result = (expensive(x) for x in mylist)
result = (do_something(x) for x in result if some_condition(x))
...
result = [x for x in result if x]  # finally, a list
  1. Ciò rende completamente chiaro ciò che sta accadendo durante ogni fase della pipeline.
  2. esplicito oltre implicito
  3. Utilizza generatori ovunque fino al passaggio finale, quindi non ci sono grandi elenchi intermedi

cf: 'Trucchi del generatore per programmatori di sistema' di David Beazley

Potresti sempre memoize la funzione costosa () così che chiamarlo la seconda volta è semplicemente una ricerca del valore calcolato di x .

Ecco solo una delle molte implementazioni di memoize come decoratore .

Puoi memorizzare costosi (x) (e se chiami spesso (x) frequentemente, probabilmente dovresti memorizzarli in qualsiasi modo. Questa pagina fornisce un'implementazione di memoize per python:

http://code.activestate.com/recipes/52201/

Ciò ha l'ulteriore vantaggio che è possibile eseguire il costoso (x) meno di N volte, poiché qualsiasi voce duplicata utilizzerà il memo dell'esecuzione precedente.

Si noti che ciò presuppone che la costosa (x) sia una vera funzione e non dipende dallo stato esterno che può cambiare. Se il costoso (x) dipende dallo stato esterno e puoi rilevare quando lo stato cambia o lo sai che non cambierà durante la comprensione dell'elenco, puoi ripristinare i memo prima della comprensione.

Avrò una preferenza per:

itertools.ifilter(bool, (expensive(x) for x in mylist))

Questo ha il vantaggio di:

Esiste semplicemente il vecchio uso di un ciclo for da aggiungere a un elenco:

result = []
for x in mylist:
    expense = expensive(x)
    if expense:
        result.append(expense)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top