web-dev-qa-db-fra.com

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

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

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

Code:

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

47
bandana

Vous pouvez le faire dans un seul revêtement en utilisant des générateurs:

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

Ce qui est bien avec les générateurs, c'est qu'ils ne calculent que jusqu'au montant demandé. Donc, demander les deux premiers indices est (presque) tout aussi simple:

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

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

83
Paul

@ La réponse acceptée de Paul est la meilleure, mais voici une petite variante à réflexion 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, le but de X est de changer la signification de "l'égalité" de la normale à "satisfait ce prédicat", permettant ainsi l'utilisation de prédicats dans toutes sortes de situations 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):, la plus courte if X(is_odd) in l:, etc.

Vaut-il la peine? Pas quand une approche plus explicite comme celle adoptée par @Paul est tout aussi pratique (surtout lorsqu'elle est modifiée pour utiliser la nouvelle fonction intégrée next plutôt que l'ancienne, la méthode .next Moins appropriée) , comme je le suggère dans un commentaire à cette réponse), mais il existe d'autres situations où cela (ou d'autres variantes de l'idée "Ajuster le sens de l'égalité", et peut-être d'autres comparateurs et/ou hachage) peut être approprié. Surtout, il vaut la peine de connaître l'idée, pour éviter d'avoir à l'inventer un jour à partir de zéro ;-).

12
Alex Martelli

Une possibilité est la fonction intégrée énumération :

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

Il est typique de faire référence à une fonction comme celle que vous décrivez comme un "prédicat"; il renvoie vrai ou faux pour une question. C'est pourquoi je l'appelle pred dans mon exemple.

Je pense également qu'il serait préférable de renvoyer None, car c'est la vraie réponse à la question. L'appelant peut choisir d'exploser sur None, si nécessaire.

11

Pas une seule fonction, 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 la liste entière à la 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
>>> 

Cependant, l'utilisation d'une expression de générateur est probablement plus lisible que itertools.

Remarque en Python3, map et 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
4
Steve Losh

Une variation sur la réponse d'Alex. Cela évite d'avoir à taper X chaque fois que vous souhaitez utiliser is_odd ou quel que soit le 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
2
John La Rooy

vous pouvez le faire avec une liste de compréhension:

l = [8,10,4,5,7]
filterl = [a for a in l if a % 2 != 0]

Ensuite, filterl retournera tous les membres de la liste remplissant l'expression a% 2! = 0. Je dirais une méthode plus élégante ...

1
Vincent Osinga