Obtenga el primer elemento de un iterable que coincida con una condición

Me gustaría obtener el primer elemento de una lista que coincida con una condición. Es importante que el método resultante no procese la lista completa, que podría ser bastante grande. Por ejemplo, la siguiente función es adecuada:

def first(the_iterable, condition = lambda x: True): for i in the_iterable: if condition(i): return i 

Esta función podría ser utilizada algo como esto:

 >>> first(range(10)) 0 >>> first(range(10), lambda i: i > 3) 4 

Sin embargo, no puedo pensar en una buena incorporada / de una sola línea que me permita hacer esto. Particularmente no quiero copiar esta función si no tengo que hacerlo. ¿Existe una forma integrada de obtener el primer elemento que coincida con una condición?

En Python 2.6 o mejor:

Si desea que StopIteration se StopIteration si no se encuentra ningún elemento coincidente:

next(x for x in the_iterable if x > 3)

Si desea que se devuelva default_value (por ejemplo, None ):

next( (x for x in the_iterable if x>3), default_value)

Tenga en cuenta que, en este caso, necesita un par de paréntesis adicionales alrededor de la expresión del generador, que se necesitan siempre que la expresión del generador no sea el único argumento.

Veo que la mayoría de las respuestas ignora con decisión la next integrada y, por lo tanto, asumo que, por alguna razón misteriosa, están enfocadas al 100% en las versiones 2.5 y anteriores, sin mencionar el problema de la versión de Python (pero luego no veo esa mención en las respuestas que mencionan la next incorporada, es por eso que pensé que era necesario proporcionar una respuesta por mi cuenta, al menos el problema de la “versión correcta” queda registrado de esta manera ;-).

En la .next() 2.5, el método .next() de los iteradores genera inmediatamente StopIteration si el iterador finaliza de inmediato, es decir, para su caso de uso, si ningún elemento en el iterable cumple la condición. Si no le importa (es decir, sabe que debe haber al menos un elemento satisfactorio), simplemente use .next() (lo mejor en un genexp, línea para el next Python 2.6 incorporado y superior).

Si le importa, encajar las cosas en una función como había indicado por primera vez en su Q parece ser el mejor, y aunque la implementación de la función que propuso está bien, también puede usar itertools , un for...: break loop o un genexp , o un try/except StopIteration como el cuerpo de la función, como se sugiere en varias respuestas. No hay mucho valor agregado en ninguna de estas alternativas, así que prefiero la versión simple que usted propuso por primera vez.

Como una función reutilizable, documentada y probada.

 def first(iterable, condition = lambda x: True): """ Returns the first item in the `iterable` that satisfies the `condition`. If the condition is not given, returns the first item of the iterable. Raises `StopIteration` if no item satysfing the condition is found. >>> first( (1,2,3), condition=lambda x: x % 2 == 0) 2 >>> first(range(3, 100)) 3 >>> first( () ) Traceback (most recent call last): ... StopIteration """ return next(x for x in iterable if condition(x)) 

Al igual que con ifilter , puedes usar una expresión generadora:

 >>> (x for x in xrange(10) if x > 5).next() 6 

En cualquier caso, es probable que desee capturar StopIteration , en caso de que ningún elemento satisfaga su condición.

Técnicamente hablando, supongo que podrías hacer algo como esto:

 >>> foo = None >>> for foo in (x for x in xrange(10) if x > 5): break ... >>> foo 6 

Evitaría tener que hacer un try/except block. Pero eso parece algo oscuro y abusivo para la syntax.

Malditas excepciones!

Me encanta esta respuesta . Sin embargo, dado que next() StopIteration una excepción StopIteration cuando no hay elementos, usaría el siguiente fragmento de código para evitar una excepción:

 a = [] item = next((x for x in a), None) 

Por ejemplo,

 a = [] item = next(x for x in a) 

Levantará una excepción de StopIteration ;

 Traceback (most recent call last): File "", line 1, in  StopIteration 

El módulo itertools contiene una función de filtro para iteradores. El primer elemento del iterador filtrado se puede obtener llamando a next() en él:

 from itertools import ifilter print ifilter((lambda i: i > 3), range(10)).next() 

Para versiones anteriores de Python donde no existe la siguiente función incorporada:

 (x for x in range(10) if x > 3).next() 

Yo escribiria esto

 next(x for x in xrange(10) if x > 3) 

La forma más eficiente en Python 3 es una de las siguientes (usando un ejemplo similar):

Con estilo de “comprensión” :

 next(i for i in range(100000000) if i == 1000) 

ADVERTENCIA : La expresión también funciona con Python 2, pero en el ejemplo se usa el range que devuelve un objeto iterable en Python 3 en lugar de una lista como Python 2 (si desea construir un iterable en Python 2 use xrange ).

Tenga en cuenta que la expresión evitar construir una lista en la expresión de comprensión next([i for ...]) , causaría la creación de una lista con todos los elementos antes de filtrar los elementos y causaría el procesamiento de todas las opciones, en su lugar de detener la iteración una vez i == 1000 .

Con estilo “funcional” :

 next(filter(lambda i: i == 1000, range(100000000))) 

ADVERTENCIA : Esto no funciona en Python 2, incluso reemplazando el range con xrange debido a que el filter crea una lista en lugar de un iterador (ineficiente), y la next función solo funciona con iteradores.

Valor por defecto

Como se mencionó en otras respuestas, debe agregar un parámetro adicional a la función next si desea evitar una excepción provocada cuando la condición no se cumple.

Estilo “funcional” :

 next(filter(lambda i: i == 1000, range(100000000)), False) 

Estilo de “comprensión” :

Con este estilo, debe rodear la expresión de comprensión con () para evitar que SyntaxError: Generator expression must be parenthesized if not sole argument :

 next((i for i in range(100000000) if i == 1000), False) 

Mediante el uso

 (index for index, value in enumerate(the_iterable) if condition(value)) 

uno puede verificar la condición del valor del primer elemento en the_iterable , y obtener su índice sin la necesidad de evaluar todos los elementos en the_iterable .

La expresión completa a usar es

 first_index = next(index for index, value in enumerate(the_iterable) if condition(value)) 

Aquí first_index asume el valor del primer valor identificado en la expresión explicada anteriormente.

Esta pregunta ya tiene grandes respuestas. Solo estoy agregando mis dos centavos porque aterricé aquí tratando de encontrar una solución a mi propio problema, que es muy similar al OP.

Si desea encontrar el ÍNDICE del primer elemento que coincida con un criterio usando generadores, simplemente puede hacer:

 next(index for index, value in enumerate(iterable) if condition) 

Dado que ha solicitado una línea única integrada, esto evitará el problema de una excepción StopIteration , aunque requiere que su iterable sea pequeño para que pueda convertirlo en una lista, ya que esa es la única construcción que conozco se tragará un StopIteration y le permitirá echar un vistazo a los valores:

 (lambda x:x[0] if x else None)(list(y for y in ITERABLE if CONDITION)) 

(Si ningún elemento coincide, obtendrá None lugar de una excepción de StopIteration ).

También puedes usar la función argwhere en Numpy. Por ejemplo:

i) Encuentra la primera “l” en “helloworld”:

 import numpy as np l = list("helloworld") # Create list i = np.argwhere(np.array(l)=="l") # i = array([[2],[3],[8]]) index_of_first = i.min() 

ii) Encontrar el primer número aleatorio> 0.1

 import numpy as np r = np.random.rand(50) # Create random numbers i = np.argwhere(r>0.1) index_of_first = i.min() 

iii) Encuentra el último número aleatorio> 0.1

 import numpy as np r = np.random.rand(50) # Create random numbers i = np.argwhere(r>0.1) index_of_last = i.max() 

Un trazador de líneas:

 thefirst = [i for i in range(10) if i > 3][0] 

Si no está seguro de que algún elemento sea válido de acuerdo con los criterios, debe incluir esto con try/except ya que [0] puede generar un IndexError .

En Python 3:

 a = (None, False, 0, 1) assert next(filter(None, a)) == 1 

En Python 2.6:

 a = (None, False, 0, 1) assert next(iter(filter(None, a))) == 1 

EDITAR: pensé que era obvio, pero aparentemente no: en lugar de None , puede pasar una función (o un lambda ) con una verificación de la condición:

 a = [2,3,4,5,6,7,8] assert next(filter(lambda x: x%2, a)) == 3