Як знайти останнє входження елемента до списку Python


80

Скажімо, у мене є цей список:

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

Наскільки мені показала допомога, не існує вбудованої функції, яка повертає останнє входження рядка (наприклад, зворотне значення index). Отже, як я можу знайти останнє входження "a"у наведеному списку?

Відповіді:


96

Якщо ви насправді використовуєте лише окремі літери, як показано у вашому прикладі, це str.rindexбуде працювати зручно. Це піднімає a, ValueErrorякщо такого елемента немає, той самий клас помилки, list.indexщо і підняття. Демо:

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

Для більш загального випадку ви можете скористатися list.indexв зворотному списку:

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

Нарізка тут створює копію всього списку. Це добре для коротких списків, але у випадку, коли liдуже велика, ефективність може бути кращою за допомогою ледачого підходу:

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

Однолінійна версія:

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

2
YMMV, але для цього прикладу len(li) - next(i for i, v in enumerate(reversed(li), 1) if v == 'a')для мене трохи швидше
John La Rooy

11
є str.rindex(), жодної причини немає list.rindex()?
Chris_Rands

2
@Chris_Rands: Краще, ніж range(len(li)-1, -1, -1)є reversed(range(len(li)))або range(len(li))[::-1](вони також приблизно еквівалентні, на відміну від більшості порівнянь між reversedта реверсивним фрагментом; сучасний Py3 rangeможна нарізати навпаки, щоб зробити ще одного ледачого, rangeякий працює назад, тому він однаково продуктивний). Схоже, Вім пішов з колишнім.
ShadowRanger

@ShadowRanger так, Вім відредагував свою відповідь, оскільки я написав цей коментар. це була відповідь на той час stackoverflow.com/revisions/ ... Я
видалю

Пітонічна версіяnext(len(a)-i-1 for i, x in enumerate(reversed(a)) if x == 3)
coder.in.me

37

Однокласний лайнер, подібний до Ігнасіо, за винятком трохи простішого / зрозумілішого

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

Мені здається цілком зрозумілим та пітонічним: ви шукаєте найвищий індекс, що містить відповідне значення. Не потрібні додатки, лямбда, реверси або інструменти.


7
Як зазначає @Isaac, це завжди повторюється над усіма N елементами li.
smci

1
Це ідеально. Можливо, не ідеально для великого набору даних, але для невеликого обсягу даних він чудовий.
srock

суттєвим мінусом є той факт, що ви завжди будете переглядати весь список. ви повинні наголосити на цьому у своїй відповіді.
gyarad

17

Багато інших рішень вимагають перегляду всього списку. Це не так.

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

Редагувати: З огляду на минуле це здається непотрібним чаклунством. Замість цього я зробив би щось подібне:

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

Я підозрюю, що різниця полягає в циклі C проти циклу Python.
Аміт Найду

8
>>> (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
Чому ні reversed(enumerate(li))?
Ісаак,

31
Тому що мені це не спало на думку 3 роки тому.
Ігнасіо Васкес-Абрамс,

Але дивно, але це reversed(enumerate(li))призводить до помилки, яка читається argument to reversed() must be a sequence! І це говорить, що reversed((y for y in enumarete(li))теж!
trss

2
Має сенс, що reversed()взагалі не може працювати з ітераторами, включаючи генератори. Отже, enumerate(reversed(li))і коригування компонента індексу перерахованих кортежів є обхідним шляхом, який дозволяє уникнути створення копії списку.
trss

7

Мені подобаються як відповіді Віма, так і Ігнасіо . Однак я думаю, що itertoolsце трохи читабельніша альтернатива, незважаючи на лямбду. (Для Python 3; для Python 2 використовуйте xrangeзамість range).

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

Це спричинить StopIterationвиняток, якщо предмет не знайдено; Ви можете схопити це і ValueErrorзамість цього підняти a , щоб зробити це так самоindex .

Визначається як функція, уникаючи lambdaярлика:

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

Це працює і для не символів. Перевірено:

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

Python 3 тепер дозволяє аргумент за замовчуванням next, тому ви можете усунутиtry... except
PM 2Ring

@ PM2Ring, я не знаю, чому ти хотів би використовувати тут аргумент за замовчуванням. Тоді вам потрібно мати ifзаяву і підняти ValueErrorвсередині цього. (Згадайте, що ми намагаємося indexточно продублювати API, що означає підняти a, ValueErrorякщо елемент не вдається знайти.) Використання tryв цьому контексті є більш ідіоматичним і, я гадаю, більш ефективним.
senderle

Чесний дзвінок. Може підняти, IndexErrorа не ValueError, щоб бути узгодженим з .index?
PM 2, кільце,

IndexErrorпризначений для передачі неправильного індексу. Передача відсутнього значення indexгенерує a ValueError.
senderle

5

С dict

Ви можете використовувати той факт, що ключі словника унікальні, і при побудові ключа з кортежами буде використано лише останнє призначення значення для певного ключа. Як зазначено в інших відповідях, це добре для невеликих списків, але створює словник для всіх унікальних значень, і для великих списків може бути неефективним.

dict(map(reversed, enumerate(li)))["a"]

6

2

Я прийшов сюди, сподіваючись виявити, що хтось уже виконав роботу над написанням найефективнішої версії list.rindex, яка надає повний інтерфейс list.index(включаючи необов’язкові startта stopпараметри). Я не знайшов цього у відповідях на це питання, ні тут , ні тут , ні тут . Тож я сам це зібрав ... використовуючи пропозиції інших відповідей на це та інші запитання.

def rindex(seq, value, start=None, stop=None):
  """L.rindex(value, [start, [stop]]) -> integer -- return last index of value.
  Raises ValueError if the value is not present."""
  start, stop, _ = slice(start, stop).indices(len(seq))
  if stop == 0:
    # start = 0
    raise ValueError('{!r} is not in list'.format(value))
  else:
    stop -= 1
    start = None if start == 0 else start - 1
  return stop - seq[stop:start:-1].index(value)

Використовувана техніка len(seq) - 1 - next(i for i,v in enumerate(reversed(seq)) if v == value), запропонована в декількох інших відповідях, може бути більш просторою: для неї не потрібно створювати зворотну копію повного списку. Але під час мого (невимушеного, випадкового) тестування це приблизно на 50% повільніше.


0

Використовуйте простий цикл:

def reversed_index(items, value):
    for pos, curr in enumerate(reversed(items)):
        if curr == value:
            return len(items) - pos - 1
    raise ValueError("{0!r} is not in list".format(value))

0
last_occurence=len(yourlist)-yourlist[::-1].index(element)-1

просто так. немає необхідності імпортувати або створювати функцію.



0

Якщо список невеликий, ви можете обчислити всі індекси і повернути найбільший:

index = max(i for i, x in enumerate(elements) if x == 'foo')

-1
def rindex(lst, val):
    try:
        return next(len(lst)-i for i, e in enumerate(reversed(lst), start=1) if e == val)
    except StopIteration:
        raise ValueError('{} is not in list'.format(val))

-1

val = [1,2,2,2,2,2,4,5].

Якщо вам потрібно знайти останній випадок 2

last_occurence = (len(val) -1) - list(reversed(val)).index(2)


-1

Ось маленький однорядковий рядок для отримання останнього індексу, використання enumerateта розуміння списку:

li = ["a", "b", "a", "c", "x", "d", "a", "6"]
[l[0] for l in enumerate(li) if l[1] == "a"][-1]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.