88 votes

Comment trouver la dernière occurrence d'un élément dans une liste Python ?

Disons que j'ai cette liste :

li = ["a", "b", "a", "c", "x", "d", "a", "6"]

D'après ce que l'aide m'a montré, il n'y a pas de fonction intégrée qui renvoie la dernière occurrence d'une chaîne de caractères (comme l'inverse de index ). Donc, en gros, comment puis-je trouver la dernière occurrence de "a" dans la liste donnée ?

110voto

wim Points 35274

Si vous n'utilisez que des lettres simples comme dans votre exemple, alors str.rindex fonctionnerait sans problème. Cela soulève une ValueError s'il n'y a pas d'élément de ce type, la même classe d'erreur que list.index soulèverait. Démonstration :

>>> li = ["a", "b", "a", "c", "x", "d", "a", "6"]
>>> ''.join(li).rindex('a')
6

Pour le cas plus général, vous pouvez utiliser list.index sur la liste inversée :

>>> len(li) - 1 - li[::-1].index('a')
6

Le découpage ici crée un copie de la liste entière. C'est très bien pour les listes courtes, mais dans le cas où li est très grande, l'efficacité peut être meilleure avec une approche paresseuse :

def list_rindex(li, x):
    for i in reversed(range(len(li))):
        if li[i] == x:
            return i
    raise ValueError("{} is not in list".format(x))

Une version en une ligne :

next(i for i in reversed(range(len(li))) if li[i] == 'a')

2 votes

YMMV, mais pour cet exemple, len(li) - next(i for i, v in enumerate(reversed(li), 1) if v == 'a') est un peu plus rapide pour moi

14 votes

Il y a str.rindex() pour une raison quelconque, il n'y a pas list.rindex() ?

2 votes

@Chris_Rands : Mieux que range(len(li)-1, -1, -1) est soit reversed(range(len(li))) o range(len(li))[::-1] (ils sont aussi à peu près équivalents, contrairement à la plupart des comparaisons entre reversed et la tranche inversée ; moderne Py3 range peut être découpé à l'envers pour faire un autre paresseux range qui fonctionne à l'envers, donc tout aussi performant). On dirait que Wim a choisi la première solution.

41voto

alcalde Points 376

Une phrase semblable à celle d'Ignacio, mais un peu plus simple/claire, serait la suivante

max(loc for loc, val in enumerate(li) if val == 'a')

Cela me semble très clair et pythonique : vous cherchez l'indice le plus élevé qui contient une valeur correspondante. Pas besoin de nexts, lambdas, reverseds ou itertools.

7 votes

Comme @Isaac l'a fait remarquer, cela itère toujours sur tous les N éléments de li.

1 votes

C'est parfait. Peut-être pas idéal pour un grand ensemble de données, mais pour une petite quantité de données, c'est génial.

0 votes

Un inconvénient important est le fait que vous allez toujours itérer à travers la liste entière. vous devriez le souligner dans votre réponse.

18voto

Isaac Points 728

De nombreuses autres solutions nécessitent d'itérer sur l'ensemble de la liste. Ce n'est pas le cas avec cette solution.

def find_last(lst, elm):
  gen = (len(lst) - 1 - i for i, v in enumerate(reversed(lst)) if v == elm)
  return next(gen, None)

Edit : Avec le recul, cela semble être de la sorcellerie inutile. Je ferais plutôt quelque chose comme ça :

def find_last(lst, sought_elt):
    for r_idx, elt in enumerate(reversed(lst)):
        if elt == sought_elt:
            return len(lst) - 1 - r_idx

0 votes

Je soupçonne que la différence est une boucle C par rapport à une boucle Python.

8voto

>>> (x for x in reversed([y for y in enumerate(li)]) if x[1] == 'a').next()[0]
6

>>> len(li) - (x for x in (y for y in enumerate(li[::-1])) if x[1] == 'a').next()[0] - 1
6

2 votes

Pourquoi pas ? reversed(enumerate(li)) ?

36 votes

Parce que ça ne m'est pas venu à l'esprit il y a 3 ans.

0 votes

Bizarrement, cependant, reversed(enumerate(li)) donne lieu à une erreur qui se lit comme suit argument to reversed() must be a sequence ! Et il est dit que pour reversed((y for y in enumarete(li)) aussi !

7voto

senderle Points 41607

J'aime les deux de wim y Ignacio réponses. Cependant, je pense que itertools fournit une alternative légèrement plus lisible, malgré les lambdas. (Pour Python 3 ; pour Python 2, utilisez xrange au lieu de range ).

>>> from itertools import dropwhile
>>> l = list('apples')
>>> l.index('p')
1
>>> next(dropwhile(lambda x: l[x] != 'p', reversed(range(len(l)))))
2

Cela entraînera la création d'un StopIteration si l'élément n'est pas trouvé ; vous pouvez l'attraper et déclencher une exception de type ValueError à la place, pour que cela se comporte juste comme index .

Défini comme une fonction, évitant le lambda raccourci :

def rindex(lst, item):
    def index_ne(x):
        return lst[x] != item
    try:
        return next(dropwhile(index_ne, reversed(range(len(lst)))))
    except StopIteration:
        raise ValueError("rindex(lst, item): item not in list")

Cela fonctionne aussi pour les non-chars. Testé :

>>> rindex(['apples', 'oranges', 'bananas', 'apples'], 'apples')
3

0 votes

Python 3 permet désormais d'ajouter un argument par défaut à l'option next pour que vous puissiez éliminer le try... except

0 votes

@PM2Ring, je ne vois pas pourquoi vous voudriez utiliser une arg par défaut ici. Vous auriez alors besoin d'avoir un if et soulève un ValueError à l'intérieur de ça. (Rappelez-vous que nous essayons de reproduire la index exactement, ce qui signifie qu'il faut lever un ValueError si l'élément est introuvable). Utilisation de try dans ce contexte est plus idiomatique et, je suppose, plus efficace.

0 votes

Appel juste. Peut-être relancer IndexError plutôt que ValueError pour être cohérent avec .index ?

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