149 votes

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 laid :

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

J'ai pensé à le changer en :

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 cela retournait un None plutôt que de lever une exception si aucune correspondance n'est trouvée.

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

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

Mais il est tout à fait insipide de commencer à remplir le code avec des fonctions utilitaires comme celle-ci (et les gens ne remarqueront probablement pas qu'elles sont déjà là, donc elles ont tendance à être répétées au fil du temps) s'il existe des modules intégrés qui fournissent déjà la même chose.

214voto

J.F. Sebastian Points 102961

Pour trouver le premier élément d'une séquence seq qui correspond à un predicate :

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

Ou simplement :

Python 2 :

next(itertools.ifilter(predicate, seq))

Python 3 :

next(filter(predicate, seq))

Ceux-ci soulèveront un StopIteration exception si le prédicat ne correspond à aucun élément.


Pour revenir None s'il n'existe pas d'élément de ce type :

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

Ou :

next(filter(predicate, seq), None)

87voto

Chewie Points 2948

Vous pourriez utiliser une expression de générateur avec une valeur par défaut et ensuite next il :

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

Cependant, pour cette ligne unique, vous devez utiliser Python >= 2.6.

Cet article plutôt populaire approfondit cette question : La fonction Python find-in-list la plus propre ? .

7voto

mac Points 16282

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

Dans mon propre code, je l'implémenterais cependant de la manière suivante :

(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 une seule fois avec [] .

1voto

parity3 Points 51

La réponse de J.F. Sebastian est la plus élégante mais nécessite python 2.6 comme l'a souligné fortran.

Pour la version Python < 2.6, voici ce que je peux faire de mieux :

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

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

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

MISE À JOUR : Bien que j'utilise personnellement une fonction prédéfinie appelée first() qui attrape un StopIteration et renvoie None, voici une amélioration possible par rapport à l'exemple ci-dessus : éviter d'utiliser filter / ifilter :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X