Как найти последнее вхождение элемента в список Python

Скажи, у меня есть этот список:

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

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

18 ответов

Решение

Если вы на самом деле используете только отдельные буквы, как показано в вашем примере, то str.rindex будет работать легко Это поднимает 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 очень большой, эффективность может быть лучше с ленивым подходом:

# Python 3 version
next(i for i, val in zip(range(len(li)-1, -1, -1), reversed(li)) if val == 'a')

# Python 2 version
from itertools import izip
indices = xrange(len(li)-1, -1, -1)
gen = izip(indices, reversed(li))
next(i for i, val in gen if val == 'a')

Однострочный, как у Игнасио, за исключением того, что он немного проще / понятнее

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

Это кажется очень ясным и Pythonic для меня: вы ищете самый высокий индекс, который содержит совпадающее значение. Никаких nexts, lambdas, полностью измененных или itertools не требуется.

Многие другие решения требуют итерации по всему списку. Это не так.

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

Мне нравятся ответы Вим и Игнасио. Тем не менее, я думаю, 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 вместо этого, чтобы заставить это вести себя так же, как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

Вы можете использовать тот факт, что словарные ключи являются уникальными, и при создании ключа с кортежами будет использоваться только последнее присвоение значения для определенного ключа. Как указано в других ответах, это подходит для небольших списков, но создает словарь для всех уникальных значений и может быть неэффективным для больших списков.

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

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

просто, как то. нет необходимости импортировать или создать функцию.

Здесь много ответов, поэтому я подумал, что было бы интересно сравнить разные подходы. Я также придумал решение, которого здесь нет, и которое работает довольно хорошо.

Давайте перейдем непосредственно к делу: вот код, который я использовал для тестирования:

      from timeit import timeit
from itertools import dropwhile


def rindex1(li, value):
    return len(li) - 1 - li[::-1].index(value)


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


def rindex3(li, value):
    return max(loc for loc, val in enumerate(li) if val == value)


def rindex4(li, value):
    for r_idx, elt in enumerate(reversed(li)):
        if elt == value:
            return len(li) - 1 - r_idx


def rindex5(li, value):
    return next(dropwhile(lambda x: li[x] != value, reversed(range(len(li)))))


def rindex6(li, value):
    return dict(map(reversed, enumerate(li)))[value]


def rindex7(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)


def my_rindex(li, value, start=0, stop=None, /):
    size = len(li)
    li.reverse()
    try:
        return (
            size - 1 - li.index(value, 0 if stop is None else size - stop, size - start)
        )
    finally:
        li.reverse()


FUNCTIONS = (rindex1, rindex2, rindex3, rindex4, rindex5, rindex6, rindex7, my_rindex)
FUNCTIONS_WITH_BOUNDARIES = (rindex7, my_rindex)


def test_correctness():
    li = [0, 1, 0, 2]
    for f in FUNCTIONS:
        assert f(li, 0) == 2
    for f in FUNCTIONS_WITH_BOUNDARIES:
        assert f(li, 0, 0, 1) == 0


def test_speed():
    small = list(range(10))
    big = list(range(1_000_000))
    tests = (
        ("small best", small, len(small) - 1, 1_000_000),
        ("big best", big, len(big) - 1, 100),
        ("small middle", small, len(small) // 2, 1_000_000),
        ("big middle", big, len(big) // 2, 100),
        ("small worst", small, 0, 1_000_000),
        ("big worst", big, 0, 100),
    )
    for name, li, value, repeats in tests:
        print(format(f" {name} ", "=^22"))
        for f in (list.index,) + FUNCTIONS:
            print(
                f.__name__.ljust(10),
                ":",
                format(
                    timeit(
                        "f(li, value)",
                        globals={
                            "f": f,
                            "li": li,
                            "value": value
                            if f is not list.index
                            else len(li) - 1 - value,
                        },
                        number=repeats,
                    ),
                    "9f",
                ),
            )


if __name__ == "__main__":
    test_correctness()
    test_speed()

И результаты:

      ===== small best =====
index      :  0.123090
rindex1    :  0.711949
rindex2    :  1.028220
rindex3    :  3.026638
rindex4    :  1.018070
rindex5    :  2.313107
rindex6    :  8.432069
rindex7    :  1.536008
my_rindex  :  0.740205
====== big best ======
index      :  0.000022
rindex1    :  2.318499
rindex2    :  0.000145
rindex3    : 12.669612
rindex4    :  0.000128
rindex5    :  0.000271
rindex6    : 82.330008
rindex7    :  2.269307
my_rindex  :  0.480168
==== small middle ====
index      :  0.262441
rindex1    :  0.860066
rindex2    :  1.402913
rindex3    :  3.048139
rindex4    :  1.414312
rindex5    :  3.344098
rindex6    :  8.443223
rindex7    :  1.685930
my_rindex  :  0.883925
===== big middle =====
index      :  1.806185
rindex1    :  4.086357
rindex2    :  5.881814
rindex3    : 12.669770
rindex4    :  6.372930
rindex5    : 13.820065
rindex6    : 82.669227
rindex7    :  4.091267
my_rindex  :  2.274667
==== small worst =====
index      :  0.451953
rindex1    :  1.011371
rindex2    :  1.834464
rindex3    :  3.031253
rindex4    :  1.899711
rindex5    :  4.544713
rindex6    :  8.453820
rindex7    :  1.850881
my_rindex  :  1.051001
===== big worst ======
index      :  3.615933
rindex1    :  5.677770
rindex2    : 11.760374
rindex3    : 12.680631
rindex4    : 12.842568
rindex5    : 27.433941
rindex6    : 82.496033
rindex7    :  5.515559
my_rindex  :  3.722199

Здесь «лучший» случай означает, что искомое значение находится крайнее правое, «средний» — посередине, «худший» — крайнее левое. Технически худшим случаем было бы отсутствие искомого значения, но обработка исключений может испортить измерения.

Как видно выше, мы можем исключитьrindex3,rindex5иrindex6от конкурентов: у них нет преимуществ.

и почти одинаковы, но 4 немного быстрее для «хороших» случаев, а для «плохих» — наоборот.

rindex1вероятно, лучший вариант для небольших списков (хотя он лишь немного медленнее).

Для больших списков, где ожидается, что элемент будет находиться ближе к правой стороне (менее 1/20 списка на один миллион элементов), лучше всего (rindex4немного превосходит его только на первой 1/1000 части).

Для остальных случаев является самым быстрым (даже сравнимым со встроеннымindexв «большом худшем» случае). Другими преимуществами этого являются реверсирование на месте (копия не создается) и поддержка границ.

Однако если вы используете границы, которые эффективно сокращают большой список до маленького,rindex7будет твоим другом.

Бонус: гибридmy_rindexкоторый использует оптимистическийrindex2-как поиск по запросу

      def my_rindex(li, value, start=0, stop=None, /, *, optimism=0):
    size = len(li)
    if optimism:
        for i in range(
            (stop if stop is not None and stop < size else size) - 1, start - 1, -1
        )[:optimism]:
            if li[i] == value:
                return i
    li.reverse()
    try:
        return (
            size - 1 - li.index(value, 0 if stop is None else size - stop, size - start)
        )
    finally:
        li.reverse()

Я пришел сюда в надежде найти кого-то, кто уже выполнил работу по написанию самой эффективной версии 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% медленнее.

Люблю решение @alcalde, но столкнулся с ValueError: max() arg является пустой последовательностью, если ни один из элементов не соответствует условию.

Чтобы избежать ошибки, установите default=None:

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

Используйте простой цикл:

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))
lastIndexOf = lambda array, item: len(array) - (array[::-1].index(item)) - 1

Вот функция для поиска последнего вхождения элемента в список. В функцию передаются список и элемент.

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

def last_occurrence(li,element):
    for i in range(len(li)-1,0,-1):
        if li[i] == element:
            return i

    return -1

last_occ = last_occurrence(li, element)
if (last_occ != -1):
    print("The last occurrence at index : ",last_occ)
else:
    print("Element not found")

Внутри last_occurrenceфункция forпетля используется с range. который будет перебирать список в обратном порядке. если элемент текущего индекса соответствует элементу, функция вернет index. В случае, если после сравнения всех элементов списка searchedэлемент не найден, функция вернет -1.

from array import array
fooa = array('i', [1,2,3])
fooa.reverse()  # [3,2,1]
fooa.index(1)
>>> 2

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

index = max(i for i, x in enumerate(elements) if x == 'foo')
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))

Вот небольшая строка для получения последнего индекса, используя enumerate и понимание списка:

li = ["a", "b", "a", "c", "x", "d", "a", "6"]
[l[0] for l in enumerate(li) if l[1] == "a"][-1]

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

Если вам нужно найти последнее вхождение 2

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

Другие вопросы по тегам