如何在 Python 列表中查找某项的最后一次出现
- 2025-02-20 09:24:00
- admin 原创
- 69
问题描述:
假设我有这个列表:
li = ["a", "b", "a", "c", "x", "d", "a", "6"]
据帮助显示,没有内置函数返回字符串的最后一次出现(如 的反向index
)。所以基本上,我如何"a"
在给定列表中找到 的最后一次出现?
解决方案 1:
如果您实际上只使用单个字母(如示例中所示),那么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
,使用反向迭代并避免复制可能会更有效:
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:
类似于 Ignacio 的一句话,但更简单/更清晰一点,是
max(loc for loc, val in enumerate(li) if val == 'a')
对我来说,它看起来非常清晰且符合 Python 风格:您正在寻找包含匹配值的最高索引。无需 nexts、lambdas、reverseds 或 itertools。
解决方案 3:
许多其他解决方案都需要迭代整个列表。这个不需要。
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
解决方案 4:
>>> (x for x in reversed(list(enumerate(li))) if x[1] == 'a').next()[0]
6
>>> len(li) - (x for x in enumerate(li[::-1]) if x[1] == 'a').next()[0] - 1
6
解决方案 5:
last_occurence=len(yourlist)-yourlist[::-1].index(element)-1
就这么简单。无需导入或创建函数。
解决方案 6:
我喜欢wim和Ignacio 的答案。不过,我认为itertools
提供了一个更易读的替代方案,尽管 lambda 如此。(对于 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
解决方案 7:
和dict
您可以使用这样一个事实:字典键是唯一的,当使用元组构建字典时,只会使用特定键的最后一个值分配。如其他答案所述,这对于小列表来说很好,但它会为所有唯一值创建一个字典,对于大列表来说可能效率不高。
dict(map(reversed, enumerate(li)))["a"]
6
解决方案 8:
我来这里是希望找到已经编写了最有效版本的人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%。
解决方案 9:
这里有很多答案,所以我认为比较不同的方法会很有趣。我还想出了一个这里未列出的非常好的解决方案,并创建了一个PyPI 包。
让我们直接进入正题,下面是我用于基准测试的代码:
from timeit import timeit
from itertools import dropwhile
from operator import indexOf
from rindex import rindex
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)
# Thanks Kelly Bundy for mentioning this in the comments
# https://stackoverflow.com/a/63834895
def rindex8(li, value):
return len(li) - 1 - indexOf(reversed(li), 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 = (rindex, rindex1, rindex2, rindex3, rindex4, rindex5, rindex6, rindex7, rindex8, my_rindex)
FUNCTIONS_WITH_BOUNDARIES = (rindex7, my_rindex, 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.124899
rindex : 0.168111
rindex1 : 0.711771
rindex2 : 1.042878
rindex3 : 3.031604
rindex4 : 1.018811
rindex5 : 2.309284
rindex6 : 8.428993
rindex7 : 1.536563
rindex8 : 0.714625
my_rindex : 0.751303
====== big best ======
index : 0.000020
rindex : 0.000030
rindex1 : 2.520536
rindex2 : 0.000187
rindex3 : 12.694951
rindex4 : 0.000135
rindex5 : 0.000276
rindex6 : 82.994252
rindex7 : 2.820221
rindex8 : 0.000113
my_rindex : 0.619653
==== small middle ====
index : 0.264816
rindex : 0.325221
rindex1 : 0.875725
rindex2 : 1.440296
rindex3 : 3.100110
rindex4 : 1.424884
rindex5 : 3.330751
rindex6 : 8.430511
rindex7 : 1.686265
rindex8 : 0.888111
my_rindex : 0.888112
===== big middle =====
index : 1.813371
rindex : 1.747949
rindex1 : 4.465189
rindex2 : 5.886711
rindex3 : 12.696632
rindex4 : 6.856758
rindex5 : 13.855785
rindex6 : 84.861101
rindex7 : 4.457431
rindex8 : 2.044351
my_rindex : 2.299312
==== small worst =====
index : 0.452868
rindex : 0.456035
rindex1 : 1.016495
rindex2 : 1.846306
rindex3 : 3.038056
rindex4 : 1.901025
rindex5 : 4.539291
rindex6 : 8.428476
rindex7 : 1.841232
rindex8 : 1.061607
my_rindex : 1.054495
===== big worst ======
index : 3.620210
rindex : 3.083546
rindex1 : 5.824754
rindex2 : 11.764573
rindex3 : 12.696748
rindex4 : 13.481179
rindex5 : 27.432877
rindex6 : 82.762718
rindex7 : 5.880688
rindex8 : 3.685528
my_rindex : 3.725783
此处,“最佳”情况意味着搜索的值位于最右边,“中间” - 位于中间,“最差” - 位于最左边。从技术上讲,最差情况是搜索的值不存在,但处理异常可能会扰乱测量结果。
rindex
我的rindex
软件包中的功能更出色(在某些情况下甚至优于内置功能index
),因为它是用 Cython 编写的。如果您需要最佳性能,请使用它。
纯Python函数比较:
我们还可以将rindex3
、rindex5
和排除rindex6
在竞争之外:它们不具备优势。
rindex2
和rindex4
几乎相同,但 4 在“好”情况下稍快一些,而在“坏”情况下则相反。它们也被排除在外,因为rindex8
在所有情况下都更快。
rindex1
对于小列表来说可能是最好的(但是,速度rindex8
只是my_rindex
稍微慢一点)。
对于其他情况rindex8
是最快的(甚至可以与内置的index
“最坏”情况相媲美)。
my_rindex
几乎一样快,但在“最大最佳”情况下速度会急剧下降。不过,它支持边界。
然而,如果你使用边界,有效地将大列表缩小到小列表,rindex7
就会成为你的朋友。
解决方案 10:
使用一个简单的循环:
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))
解决方案 11:
喜欢@alcalde 的解决方案,但是遇到ValueError:如果没有元素符合条件,则max() arg 是一个空序列。
为了避免错误设置default=None:
max((loc for loc, val in enumerate(li) if val == 'a'), default=None)
解决方案 12:
lastIndexOf = lambda array, item: len(array) - (array[::-1].index(item)) - 1
解决方案 13:
如果列表很小,您可以计算所有索引并返回最大的索引:
index = max(i for i, x in enumerate(elements) if x == 'foo')
解决方案 14:
def rindex(lst, val):
try:
return next(
len(lst) - n
for n, v in enumerate(reversed(lst), start=1)
if v == val
)
except StopIteration:
raise ValueError(f'{val} is not in list')
解决方案 15:
值 = [1,2,2,2,2,2,4,5].
如果你需要找到最后一次出现的 2
last_occurence = (len(val) -1) - list(reversed(val)).index(2)
解决方案 16:
enumerate
下面是使用列表推导获取最后一个索引的一行小代码:
li = ["a", "b", "a", "c", "x", "d", "a", "6"]
[l[0] for l in enumerate(li) if l[1] == "a"][-1]
解决方案 17:
最 Python 化、最有效的方式,也适用于非字符串列表:
len(li) - next((i for i, e in enumerate(reversed(li)) if e == "a"), len(li) + 1)
如果未找到,则表达式的计算结果为-1
。
无需构建新列表,100% 依赖迭代器。
扫码咨询,免费领取项目管理大礼包!