112 votes

Python : retourner l'index du premier élément d'une liste qui rend une fonction passée vraie

En list.index(x) renvoie l'indice dans la liste du premier élément dont la valeur est x .

Existe-t-il une fonction, list_func_index() , similaire à la index() qui a une fonction, f() comme paramètre. La fonction, f() est exécuté sur chaque élément, e de la liste jusqu'à ce que f(e) retours True . Ensuite list_func_index() renvoie l'index de e .

Codewise :

>>> def list_func_index(lst, func):
      for i in range(len(lst)):
        if func(lst[i]):
          return i
      raise ValueError('no element making func True')

>>> l = [8,10,4,5,7]
>>> def is_odd(x): return x % 2 != 0
>>> list_func_index(l,is_odd)
3

Existe-t-il une solution plus élégante ? (et un meilleur nom pour la fonction)

155voto

Paul Points 954

Vous pouvez le faire en une ligne à l'aide de générateurs :

next(i for i,v in enumerate(l) if is_odd(v))

L'avantage des générateurs est qu'ils ne calculent que jusqu'à la quantité demandée. Il est donc (presque) aussi facile de demander les deux premiers indices :

y = (i for i,v in enumerate(l) if is_odd(v))
x1 = next(y)
x2 = next(y)

Cependant, il faut s'attendre à une exception StopIteration après le dernier index (c'est ainsi que fonctionnent les générateurs). C'est également pratique dans votre approche "take-first", pour savoir qu'aucune valeur de ce type n'a été trouvée --- la fonction list.index() lancerait ici ValueError.

24voto

Jonathan Feinberg Points 24791

L'une des possibilités est le système intégré énumérer fonction :

def index_of_first(lst, pred):
    for i,v in enumerate(lst):
        if pred(v):
            return i
    return None

Il est courant de qualifier une fonction comme celle que vous décrivez de "prédicat" ; elle renvoie un vrai ou un faux pour une question donnée. C'est pourquoi je l'appelle pred dans mon exemple.

Je pense également qu'il serait plus judicieux de renvoyer None puisque c'est la vraie réponse à la question. L'appelant peut choisir d'exploser sur None le cas échéant.

16voto

Alex Martelli Points 330805

La réponse acceptée de @Paul est la meilleure, mais voici une petite variante de pensée latérale, principalement à des fins d'amusement et d'instruction... :

>>> class X(object):
...   def __init__(self, pred): self.pred = pred
...   def __eq__(self, other): return self.pred(other)
... 
>>> l = [8,10,4,5,7]
>>> def is_odd(x): return x % 2 != 0
... 
>>> l.index(X(is_odd))
3

essentiellement, X a pour but de changer la signification de "égalité" de la signification normale à "satisfait ce prédicat", permettant ainsi l'utilisation de prédicats dans toutes sortes de situations qui sont définies comme vérifiant l'égalité -- par exemple, il vous permettrait également de coder, au lieu de if any(is_odd(x) for x in l): , le plus court if X(is_odd) in l: et ainsi de suite.

Cela vaut-il la peine de l'utiliser ? Pas quand une approche plus explicite comme celle adoptée par @Paul est tout aussi pratique (surtout quand on la modifie pour utiliser la nouvelle et brillante interface intégrée de la next plutôt que la fonction plus ancienne et moins appropriée .next comme je le suggère dans un commentaire à cette réponse), mais il y a d'autres situations où cela (ou d'autres variantes de l'idée "modifier la signification de l'égalité", et peut-être d'autres comparateurs et/ou le hachage) peut être approprié. Cela vaut surtout la peine de connaître l'idée, pour éviter d'avoir à l'inventer de toutes pièces un jour;-).

7voto

Steve Losh Points 11958

Il ne s'agit pas d'une fonction unique, mais vous pouvez le faire assez facilement :

>>> test = lambda c: c == 'x'
>>> data = ['a', 'b', 'c', 'x', 'y', 'z', 'x']
>>> map(test, data).index(True)
3
>>>

Si vous ne voulez pas évaluer toute la liste en une seule fois, vous pouvez utiliser itertools, mais ce n'est pas aussi joli :

>>> from itertools import imap, ifilter
>>> from operator import itemgetter
>>> test = lambda c: c == 'x'
>>> data = ['a', 'b', 'c', 'x', 'y', 'z']
>>> ifilter(itemgetter(1), enumerate(imap(test, data))).next()[0]
3
>>> 

L'utilisation d'une expression de générateur est probablement plus lisible que itertools mais

Note dans Python3, map y filter renvoient des itérateurs paresseux et vous pouvez simplement utiliser :

from operator import itemgetter
test = lambda c: c == 'x'
data = ['a', 'b', 'c', 'x', 'y', 'z']
next(filter(itemgetter(1), enumerate(map(test, data))))[0]  # 3

2voto

gnibbler Points 103484

Une variante de la réponse d'Alex. Cela évite d'avoir à taper X chaque fois que vous voulez utiliser is_odd ou tout prédicat

>>> class X(object):
...     def __init__(self, pred): self.pred = pred
...     def __eq__(self, other): return self.pred(other)
... 
>>> L = [8,10,4,5,7]
>>> is_odd = X(lambda x: x%2 != 0)
>>> L.index(is_odd)
3
>>> less_than_six = X(lambda x: x<6)
>>> L.index(less_than_six)
2

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