Obtenez le premier élément d'un itérable 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 toute la liste, qui pourrait être assez large. 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 à un bon encastré / un-liner pour me laisser faire cela. Je ne veux pas particulièrement copier cette fonction si je n'avez pas à. Est-il un moyen intégré pour obtenir le premier élément correspondant à un état?

192
demandé sur davidism 2010-03-02 10:11:29

13 réponses

en Python 2.6 ou mieux:

si vous voulez que StopIteration soit soulevé si aucun élément correspondant n'est trouvé:

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

si vous voulez que default_value (par exemple None ) soit retourné à la place:

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

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

je vois la plupart des réponses ignorer résolument le next intégré et donc je suppose que pour une raison mystérieuse ils sont 100% axés sur les versions 2.5 et plus - sans mentionner la question de la version Python (mais alors je ne vois pas cette mention dans les réponses que do mentionner le next intégré, ce qui est pourquoi j'ai pensé qu'il était nécessaire de fournir une réponse moi-même -- au moins la" version correcte " est enregistrée de cette façon;-).

en 2.5, la .next() méthode des itérateurs soulève immédiatement StopIteration si l'itérateur se termine immédiatement -- c.-à-d., pour votre cas d'utilisation, si aucun article dans le itérable satisfait à la condition. Si vous ne vous souciez pas (i.e., vous savez là doit être au moins un élément satisfaisant) alors il suffit d'utiliser .next() (meilleur sur une genexp, ligne pour le next intégré dans la version 2.6 de Python et de meilleure qualité).

si vous faites attention, enveloppez les choses dans une fonction comme vous l'aviez d'abord indiqué dans votre Q semble le mieux, et bien que la mise en œuvre de la fonction que vous avez proposé est très bien, vous pouvez alternativement utiliser itertools , une for...: break boucle, ou un genexp, ou un try/except StopIteration comme le corps de la fonction, comme diverses réponses ont suggéré. Il n'y a pas beaucoup de valeur ajoutée dans aucune de ces alternatives, donc j'irais pour le plus simple version que vous avez proposée en premier.

297
répondu Alex Martelli 2017-01-18 10:54:26

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))
18
répondu Caridorc 2016-02-19 19:31:07

similaire à l'utilisation de 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 voulez probablement attraper StopIteration cependant, dans le cas où aucun élément ne satisfont à votre condition.

techniquement parlant, je suppose que vous pourriez faire quelque chose comme ça:

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

il éviterait d'avoir à faire un bloc try/except . Mais cela semble un peu obscur et abusif à la syntaxe.

13
répondu Matt Anderson 2010-03-02 07:37:42

Maudites Exceptions!

j'aime cette réponse . Toutefois, depuis next() soulever une exception StopIteration lorsqu'il n'y a pas d'articles, j'utiliserais l'extrait suivant pour éviter une exception:

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

par exemple,

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

Va créer un StopIteration l'exception";

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
9
répondu Jossef Harush 2017-05-23 11:33:26

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
répondu Menno Smits 2010-03-02 09:02:13

je voudrais écrire ce

next(x for x in xrange(10) if x > 3)
6
répondu Mike Graham 2014-09-27 14:39:10

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

from itertools import ifilter

print ifilter((lambda i: i > 3), range(10)).next()
4
répondu sth 2010-03-02 07:16:46

en utilisant

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

on peut vérifier la condition de la valeur du premier article dans the_iterable , et obtenir son index sans avoir à évaluer tous les articles dans 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 discutée ci-dessus.

4
répondu blue_note 2016-11-21 16:26:53

puisque vous avez demandé une doublure intégrée, cela évitera la question d'une exception StopIteration , bien qu'il exige que votre itérable est petit afin que vous puissiez le jeter à une liste, puisque c'est la seule construction que je connaisse qui avalera une Stopitération et vous laissera regarder 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
répondu ninjagecko 2012-04-20 04:43:10

cette question a déjà de grandes réponses. Je n'ajoute que mes deux cents parce que j'ai atterri ici en essayant de trouver une solution à mon propre problème, qui est très similaire à L'opération.

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)
0
répondu dangom 2017-05-10 15:25:55

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

avec "compréhension" style:

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

WARNING : l'expression fonctionne aussi avec Python 2, mais dans l'exemple est utilisé range qui renvoie un objet itérable en Python 3 au lieu d'une liste comme Python 2 (Si vous voulez construire un itérable en Python 2 utiliser xrange plutôt.)

noter que l'expression Éviter de construire une liste dans l'expression de compréhension next([i for ...]) , qui causerait de créer une liste avec tous les éléments avant de filtrer les éléments, et causerait de traiter l'ensemble des options, au lieu d'arrêter l'itération une fois i == 1000 .

Avec "fonctionnelle" style":

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

avertissement : cela ne fonctionne pas en Python 2, même en remplaçant range par xrange en raison du fait que filter crée une liste au lieu d'un itérateur (inefficace), et la fonction next ne fonctionne qu'avec des itérateurs.

valeur par défaut

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

"fonctionnelle" style:

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

"compréhension" style:

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)
0
répondu Mariano Ruiz 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

EDIT: j'ai pensé que c'était évident, mais apparemment pas: au lieu de None vous pouvez passer une fonction (ou un lambda ) avec un contrôle de la condition:

a = [2,3,4,5,6,7,8]
assert next(filter(lambda x: x%2, a)) == 3
-1
répondu Berislav Lopac 2018-08-13 19:17:46

Oneliner:

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

si vous n'êtes pas sûr qu'un élément sera valide selon les critères, vous devez l'inclure avec try/except puisque [0] peut soulever un IndexError .

-2
répondu Mizipzor 2010-03-02 08:54:46