Как найти последнее вхождение элемента в список 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)