web-dev-qa-db-fra.com

Trouver le premier élément d'une séquence qui correspond à un prédicat

Je veux un moyen idiomatique de trouver le premier élément d'une liste qui correspond à un prédicat.

Le code actuel est assez moche:

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

J'ai pensé à le changer pour:

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

Mais il doit y avoir quelque chose de plus élégant ... Et ce serait bien si elle renvoie une valeur None plutôt que de déclencher une exception si aucune correspondance n'est trouvée.

Je sais que je pourrais juste définir une fonction comme:

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

Mais il est assez insipide de commencer à remplir le code avec des fonctions utilitaires comme celle-ci (et les gens ne remarqueront probablement pas qu’ils sont déjà présents, ils ont donc tendance à se répéter au fil du temps) s’il existe des solutions intégrées qui fournissent déjà la même chose.

146
fortran

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

Il soulève StopIteration s'il n'y en a pas.

next(ifilter(predicate, seq), None)

retourne None s'il n'y a pas un tel élément.

207
jfs

Vous pouvez utiliser une expression génératrice avec une valeur par défaut, puis next it:

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

Bien que pour ce one-liner, vous devez utiliser Python> = 2.6.

Cet article plutôt populaire aborde plus en détail ce problème: Cleanest Python fonction de recherche dans la liste? .

85
Chewie

Je ne pense pas qu'il y ait quelque chose qui cloche dans les solutions que vous avez proposées dans votre question.

Dans mon propre code, je le mettrais en œuvre comme ceci:

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

La syntaxe avec () crée un générateur, ce qui est plus efficace que de générer toute la liste en même temps avec [].

5
mac

La réponse de J.F. Sebastian est très élégante, mais nécessite python 2.6 comme l'a souligné fortran.

Pour Python version <2.6, voici le meilleur de ce que je peux trouver:

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

Alternativement, si vous aviez besoin d'une liste plus tard (list gère le StopIteration), ou si vous aviez besoin de plus que le premier mais pas du tout, vous pouvez le faire avec islice:

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

UPDATE: Bien que j'utilise personnellement une fonction prédéfinie, appelée first (), qui intercepte une StopIteration et ne renvoie aucune, voici une amélioration possible par rapport à l'exemple ci-dessus: évitez d'utiliser filter/ifilter:

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