2011-12-16 13 views
110

Domanda stupida in anticipo: voglio un modo idiomatico per trovare il primo elemento in un elenco che corrisponde a un predicato.trova il primo elemento in una sequenza che corrisponde a un predicato

Il codice attuale è abbastanza brutto:

[x for x in seq if predicate(x)][0] 

ho pensato di cambiare a:

from itertools import dropwhile 
dropwhile(lambda x: not predicate(x), seq).next() 

ma ci deve essere qualcosa di più elegante ... e sarebbe bello se restituisce un valore None anziché generare un'eccezione se non viene trovata alcuna corrispondenza.

So che potrei solo definire una funzione come:

def get_first(predicate, seq): 
    for i in seq: 
     if predicate(i): return i 
    return None 

ma è del tutto insapore per iniziare a riempire il codice con funzioni di utilità come questo (e la gente probabilmente non si accorgono che sono già lì, in modo da tendono a essere ripetuti nel tempo) se ci sono insiemi integrati che forniscono già lo stesso.

+1

Questa non è una domanda stupida, e @ j-f-sebastian: questo non è un duplicato. Questa domanda riguarda in particolare la restituzione di un oggetto e il ritorno di "Nessuno" anziché il lancio di un'eccezione. Riguarda anche l'eleganza. L'altra domanda è più di una domanda n00b e non chiede queste cose, almeno non molto chiaramente. –

+1

Oltre a essere chiesto più tardi di "[funzione di ricerca sequenza python] (https://stackoverflow.com/questions/6039425/python-sequence-find-function)", questa domanda ha un ** titolo molto migliore **. – Wolf

risposta

159

next(x for x in seq if predicate(x))

solleva StopIteration se non c'è nessuno.

next(ifilter(predicate, seq), None)

rendimenti None se non c'è tale elemento.

+12

Oppure puoi fornire un secondo argomento "predefinito" a 'next' che viene usato invece di aumentare l'eccezione. –

+2

@fortran: ['next()'] (http://docs.python.org/library/functions.html#next) è disponibile da Python 2.6 È possibile leggere [What's New page] (http: // docs. python.org/whatsnew/2.7.html) per familiarizzare rapidamente con le nuove funzionalità. – jfs

+1

Sono un novizio pitone e leggo i documenti e l'ifilter usa il metodo "rendimento". Presumo questo significa che il predicato viene valutato pigramente mentre procediamo. non eseguiamo il predicato attraverso l'intera lista perché ho una funzione di predicato che è un po 'costosa e voglio solo iterare fino al punto in cui troviamo un elemento –

68

È possibile utilizzare un generatore di espressione con un valore di default e poi next esso:

next((x for x in seq if predicate(x)), None) 

Anche se per questo one-liner è necessario essere utilizzando Python> = 2.6.

Questo articolo piuttosto popolare discute ulteriormente questo problema: Cleanest Python find-in-list function?.

3

Non penso ci sia niente di sbagliato in entrambe le soluzioni che hai proposto nella tua domanda.

Nel mio codice, vorrei implementare in questo modo però:

(x for x in seq if predicate(x)).next() 

La sintassi con () crea un generatore, che è più efficiente di generare tutta la lista in una sola volta con [].

+0

E non solo: con '[]' potresti incontrare dei problemi se l'iteratore non finisce mai oi suoi elementi sono difficili da creare, più tardi ottiene ... – glglgl

+5

L'oggetto '' generator 'non ha attributo 'next'' su Python 3. – jfs

+0

@glglgl - Per quanto riguarda il primo punto (non finisce mai) ne dubito, poiché l'argomento è una sequenza finita [più precisamente una lista secondo la domanda dell'OP]. Per quanto riguarda il secondo: ancora una volta, dato che l'argomento fornito è una sequenza, gli oggetti dovrebbero essere già stati creati e memorizzati dal momento in cui viene chiamata questa funzione .... o mi manca qualcosa? – mac

1

J.F. La risposta di Sebastian è molto elegante ma richiede python 2.6 come indicato da fortran.

Per versione Python < 2.6, ecco il meglio che posso venire con:

from itertools import repeat,ifilter,chain 
chain(ifilter(predicate,seq),repeat(None)).next() 

In alternativa, se avete bisogno di una lista più tardi (lista gestisce la StopIteration), o avete bisogno di più di solo il primo, ma ancora non è tutto, si può farlo con iSlice:

from itertools import islice,ifilter 
list(islice(ifilter(predicate,seq),1)) 

UPDATE: Anche se io sono personalmente usando una funzione predefinita chiamata prima() che cattura uno StopIteration e restituisce None, Ecco un possibile miglioramento rispetto l'esempio precedente: evitare di utilizzare il filtro/IFilter:

from itertools import islice,chain 
chain((x for x in seq if predicate(x)),repeat(None)).next() 
+8

Yikes! se si tratta di questo, farei semplicemente il semplice ciclo "for" con un "se" al suo interno - molto più facile da leggere –

Problemi correlati