web-dev-qa-db-fra.com

Obtenir le premier élément d'un itératif qui correspond à une condition

Je voudrais obtenir le premier élément d'une liste correspondant à une condition. Il est important que la méthode résultante ne traite pas la totalité de la liste, qui pourrait être assez longue. Par exemple, la fonction suivante est adéquate:

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

Cette fonction pourrait être utilisée quelque chose comme ceci:

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

Cependant, je ne peux pas penser à une bonne ligne intégrée/une ligne pour me permettre de le faire. Je ne veux pas particulièrement copier cette fonction si je n'ai pas à le faire. Existe-t-il un moyen intégré d’obtenir le premier élément correspondant à une condition?

217
Chris Phillips

Dans Python 2.6 ou mieux:

Si vous souhaitez que StopIteration soit déclenché si aucun élément correspondant n'est trouvé:

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

Si vous souhaitez que default_value (par exemple, None) soit renvoyé à la place:

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

Notez que dans ce cas, vous avez besoin d'une paire de parenthèses supplémentaire autour de l'expression du générateur. Elles sont toujours nécessaires lorsque l'expression du générateur n'est pas le seul argument.

Je vois que la plupart des réponses ignorent résolument le next intégré et je suppose donc que, pour une raison mystérieuse, ils se concentrent à 100% sur les versions 2.5 et antérieures - sans mentionner le problème de la version Python (mais ensuite Je ne vois pas cette mention dans les réponses que fait mentionne le next intégré, raison pour laquelle j’ai jugé nécessaire de fournir moi-même une réponse - du moins le "correct" Le numéro "version" est enregistré de cette façon ;-).

Dans la version 2.5, la méthode .next() des itérateurs soulève immédiatement StopIteration si l'itérateur se termine immédiatement - c'est-à-dire, pour votre cas d'utilisation, si aucun élément de l'itéré ne remplit la condition. Si vous ne vous en souciez pas (c’est-à-dire que vous savez que il faut avoir au moins un élément satisfaisant), utilisez simplement .next() (mieux sur un genexp, ligne pour next intégré dans Python 2.6 et supérieur).

Si vous soignez , encapsuler les objets dans une fonction comme indiqué dans votre Q semble meilleur, et bien que l'implémentation de la fonction que vous proposez fonctionne parfaitement, vous pouvez également utiliser itertools, une boucle for...: break, ou un genexp, ou un try/except StopIteration en tant que corps de la fonction, comme suggéré par diverses réponses. Aucune de ces solutions n'apportant une valeur ajoutée importante, je choisirais donc la version simplissime que vous avez proposée en premier.

385
Alex Martelli

En tant que fonction réutilisable, documentée et testée

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))
19
Caridorc

Semblable à utiliser ifilter, vous pouvez utiliser une expression génératrice:

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

Dans les deux cas, vous voudrez probablement attraper StopIteration cependant, au cas où aucun élément ne satisferait votre condition.

Techniquement, je suppose que vous pourriez faire quelque chose comme ceci:

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

Cela éviterait de devoir créer un bloc try/except. Mais cela semble assez obscur et abusif pour la syntaxe.

12
Matt Anderson

Damn Exceptions!

J'aime cette réponse . Cependant, étant donné que next() lève une exception StopIteration quand il n'y a aucun élément, J'utiliserais l'extrait de code suivant pour éviter une exception:

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

Par exemple, 

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

Lève une exception StopIteration;

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
11
Jossef Harush

Pour les anciennes versions de Python où la prochaine version intégrée n'existe pas:

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

J'écrirais ceci 

next(x for x in xrange(10) if x > 3)
6
Mike Graham

Le module itertools contient une fonction de filtrage pour les itérateurs. Le premier élément de l'itérateur filtré peut être obtenu en appelant next() dessus:

from itertools import ifilter

print ifilter((lambda i: i > 3), range(10)).next()
6
sth

En utilisant 

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

on peut vérifier le condition du value du premier élément de the_iterable et obtenir son index sans avoir à évaluer tous les éléments de the_iterable .

L’expression complète à utiliser est

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

Ici first_index suppose la valeur de la première valeur identifiée dans l'expression décrite ci-dessus.

5
blue_note

Le moyen le plus efficace dans Python 3 est l’un des suivants (en utilisant un exemple similaire):

Avec le style "compréhension":

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

WARNING: L'expression fonctionne également avec Python 2, mais dans l'exemple, on utilise range qui renvoie un objet itérable dans Python 3 au lieu d'une liste comme Python 2 (si vous voulez construire un itérable dans Python 2, utilisez xrange à la place).

Notez que l'expression évite de construire une liste dans l'expression de compréhension next([i for ...]), ce qui aurait pour effet de créer une liste avec tous les éléments avant de filtrer les éléments et de traiter toutes les options au lieu d'arrêter l'itération une fois i == 1000.

Avec le style "fonctionnel":

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

WARNING: Cela ne fonctionne pas dans Python 2, même en remplaçant range par xrange car filter crée une liste au lieu d'un itérateur (inefficient), et la fonction next ne fonctionne qu'avec des itérateurs.

Valeur par défaut

Comme indiqué dans d'autres réponses, vous devez ajouter un paramètre supplémentaire à la fonction next si vous souhaitez éviter une exception déclenchée lorsque la condition n'est pas remplie.

style "fonctionnel":

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

style "compréhension":

Avec ce style, vous devez entourer l'expression de compréhension avec () pour éviter un SyntaxError: Generator expression must be parenthesized if not sole argument:

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

Cette question a déjà de bonnes réponses. J'ajoute seulement mes deux sous parce que j'ai atterri ici en essayant de trouver une solution à mon propre problème, qui est très similaire au PO. 

Si vous voulez trouver l'INDEX du premier article correspondant à un critère en utilisant des générateurs, vous pouvez simplement faire:

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

Étant donné que vous avez demandé une ligne intégrée, cela évitera le problème d'une exception StopIteration, bien que cela nécessite que votre valeur itérable soit petite afin que vous puissiez la transtyper vers une liste, car c'est la seule construction que je connaisse. avalera un StopIteration et vous permettra de jeter un coup d’œil sur les valeurs:

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

(Si aucun élément ne correspond, vous obtiendrez None plutôt qu'une exception StopIteration.)

0
ninjagecko

Vous pouvez également utiliser la fonction argwhere dans Numpy. Par exemple:

i) Trouvez le premier "l" dans "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) Trouver le premier nombre aléatoire> 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) Trouver le dernier nombre aléatoire> 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()
0
aim