Obtener 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 toda la lista, 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 usarse algo como esto:

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

Sin embargo, no puedo pensar en un buen built-in / one-liner que me permita hacer esto. No quiero particularmente copiar esta función si no tengo que hacerlo. ¿Hay una forma incorporada de obtener la primera ¿el artículo coincide con una condición?

Author: davidism, 2010-03-02

13 answers

En Python 2.6 o superior:

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

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

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

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

Tenga en cuenta que necesita un par de paréntesis extra alrededor de la expresión del generador en este caso - siempre son necesarios cuando la expresión del generador no es el único argumento.

Veo que la mayoría de las respuestas resueltamente ignoran next incorporado y así asumo que por alguna misteriosa razón están 100% enfocados en las versiones 2.5 y anteriores without sin mencionar el problema de la versión de Python (pero entonces no veo esa mención en las respuestas que hacen mencionan el next incorporado, por lo que pensé que era necesario dar una respuesta yo mismo myself al menos el problema de la "versión correcta" queda registrado de esta manera; -).

En 2.5, el .next() método de iteradores plantea inmediatamente StopIteration si el iterador termina inmediatamente finishes es decir, para su caso de uso, si ningún elemento en el iterable satisface la condición. Si no te importa (es decir, sabes que debe ser al menos un elemento satisfactorio) entonces solo usa .next() (mejor en un genexp, línea para el next incorporado en Python 2.6 y mejor).

Si hace cuidado, envolver las cosas en una función como lo había indicado por primera vez en su Q parece lo mejor, y mientras que la implementación de la función que propuso está bien, alternativamente podría usar itertools, un bucle for...: break o un genexp, o un try/except StopIteration como el cuerpo de la función, como varias respuestas sugirieron. No hay mucho valor agregado en ninguna de estas alternativas, así que elegiría la versión descarnadamente simple que propuso por primera vez.

 297
Author: Alex Martelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-18 10:54:26

Como 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))
 18
Author: Caridorc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-19 19:31:07

Similar a usar ifilter, podría 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 sin embargo, 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 bloque try/except. Pero eso parece un poco oscuro y abusivo a la sintaxis.

 13
Author: Matt Anderson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-02 07:37:42

Malditas excepciones!

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

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

Por ejemplo,

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

Generará una excepción StopIteration;

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
 9
Author: Jossef Harush,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:33:26

Para versiones anteriores de Python en las que el siguiente built-in no existe:

(x for x in range(10) if x > 3).next()
 6
Author: Menno Smits,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-02 09:02:13

Yo escribiría esto

next(x for x in xrange(10) if x > 3)
 6
Author: Mike Graham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-27 14:39:10

El itertools el módulo 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()
 4
Author: sth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-02 07:16:46

Usando

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

Uno puede comprobar la condición de la valor del primer elemento de the_iterable, y obtener su index sin la necesidad de evaluar todos los elementos de 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 discutida anteriormente.

 4
Author: blue_note,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-21 16:26:53

Dado que ha solicitado un built-in one-liner, esto evitará el problema de una excepción StopIteration, aunque requiere que su iterable sea pequeño para que pueda lanzarlo a una lista, ya que es la única construcción que conozco que tragará una 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 en lugar de una excepción StopIteration.)

 0
Author: ninjagecko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-20 04:43:10

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 a la OP.

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

next(index for index, value in enumerate(iterable) if condition)
 0
Author: dangom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-10 15:25:55

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

Con "comprensión" estilo:

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

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

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

Con "funcional" estilo:

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

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

Predeterminado valor

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

"funcional" estilo:

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

"comprensión" estilo:

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

next((i for i in range(100000000) if i == 1000), False)
 0
Author: Mariano Ruiz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-08 18:14:53

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 comprobación de la condición:

a = [2,3,4,5,6,7,8]
assert next(filter(lambda x: x%2, a)) == 3
 -1
Author: Berislav Lopac,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-13 19:17:46

Oneliner:

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

Si no está seguro de que algún elemento será válido de acuerdo con el criterio, debe encerrarlo con try/except ya que [0] puede levantar un IndexError.

 -2
Author: Mizipzor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-02 08:54:46