2011-12-16 25 views
110

Estúpida pregunta por delante: quiero una forma idiomática de encontrar el primer elemento en una lista que coincida con un predicado.encuentra el primer elemento en una secuencia que coincide con un predicado

El código actual es bastante feo:

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

He pensado en cambiar a:

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

Pero tiene que haber algo más elegante ... y sería bueno si devuelve un valor de None en lugar de generar una excepción si no se encuentra ninguna coincidencia.

sé que sólo pudiera definir una función como:

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

pero es bastante insípido para comenzar a llenar el código con funciones de utilidad como esta (y la gente probablemente no se dan cuenta de que son ya allí, por lo tienden a repetirse a lo largo del tiempo) si hay ins integrados que ya proporcionan lo mismo.

+1

Esta no es una pregunta estúpida, y @ j-f-sebastian: esto no es un duplicado. Esta pregunta es específicamente sobre devolver un objeto y devolver 'None' en lugar de arrojar una excepción. También se trata de elegancia. La otra pregunta es más una pregunta n00b y no pregunta estas cosas, al menos no muy claramente. –

+1

Además de ser preguntado más tarde que "[función de búsqueda de secuencia de python] (https://stackoverflow.com/questions/6039425/python-sequence-find-function)", esta pregunta tiene ** un título mucho mejor **. – Wolf

Respuesta

159

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

Plantea StopIteration si no hay ninguno.

next(ifilter(predicate, seq), None)

vuelve None si no existe tal elemento.

+12

O puede proporcionar un segundo argumento "predeterminado" para 'siguiente' que se usa en lugar de generar la excepción. –

+2

@fortran: ['next()'] (http://docs.python.org/library/functions.html#next) está disponible desde Python 2.6. Puede leer [la página de novedades] (http: // docs. python.org/whatsnew/2.7.html) para familiarizarse rápidamente con las nuevas funciones. – jfs

+1

Soy un principiante de Python y leo los documentos y el ifilter usa el método de "rendimiento". Supongo que esto significa que el predicado se evalúa perezosamente a medida que avanzamos. es decir, no ejecutamos el predicado a través de toda la lista porque tengo una función de predicado que es un poco costosa y quiero solo iterar hasta el punto donde encontramos un ítem –

68

Se puede usar un generador de expresión con un valor predeterminado y luego se next:

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

Aunque para esto de una sola línea que necesita utilizar Python> = 2.6.

Este artículo bastante popular discute aún más este problema: Cleanest Python find-in-list function?.

3

No creo que haya nada de malo con las soluciones que propuso en su pregunta.

En mi propio código, me gustaría ponerlo en práctica como esto sin embargo:

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

La sintaxis con () crea un generador, que es más eficiente que la generación de toda la lista a la vez con [].

+0

Y no solo eso: con '[]' podría tener problemas si el iterador nunca termina o sus elementos son difíciles de crear, más tarde se vuelve ... – glglgl

+5

''generator' object no tiene atributo 'next'' en Python 3. – jfs

+0

@glglgl - En cuanto al primer punto (nunca termina) lo dudo, ya que el argumento es una secuencia finita [más precisamente una lista según la pregunta OP']. En cuanto al segundo: una vez más, dado que el argumento proporcionado es una secuencia, los objetos ya deberían haber sido creados y almacenados para cuando se llame a esta función ... ¿o me falta algo? – mac

1

J.F. La respuesta de Sebastian es muy elegante, pero requiere el pitón 2.6 como señaló fortran.

Para Python versión < 2.6, esto es lo mejor que puedo llegar a:

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

Alternativamente, si usted necesita una lista más adelante (la lista se encarga de la StopIteration), o si necesita algo más que la primera, pero no todos, puede hacerlo con iSlice:

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

ACTUALIZACIÓN: Aunque personalmente estoy usando una función predefinida llamada primero() que detecta un StopIteration y devuelve None, he aquí una posible mejora con respecto al ejemplo anterior: evitar el uso de filtros/ifilter:

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

¡Yikes! si se trata de eso, simplemente haría el bucle "para" simple con un "si" dentro, mucho más fácil de leer –

Cuestiones relacionadas