Reputation: 7684
Say I have this list:
li = ["a", "b", "a", "c", "x", "d", "a", "6"]
As far as help showed me, there is not a builtin function that returns the last occurrence of a string (like the reverse of index
). So basically, how can I find the last occurrence of "a"
in the given list?
Upvotes: 125
Views: 149126
Reputation: 363476
If you are actually using just single letters like shown in your example, then str.rindex
would work handily. This raises a ValueError
if there is no such item, the same error class as list.index
would raise. Demo:
>>> li = ["a", "b", "a", "c", "x", "d", "a", "6"]
>>> ''.join(li).rindex('a')
6
For the more general case you could use list.index
on the reversed list:
>>> len(li) - 1 - li[::-1].index('a')
6
The slicing here creates a copy of the entire list. That's fine for short lists, but for the case where li
is very long, it may be more efficient to use a reverse iteration and avoid the copy:
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))
One-liner version:
next(i for i in reversed(range(len(li))) if li[i] == 'a')
Upvotes: 137
Reputation: 9390
Most Pythonic and efficient way and also applicable to non-string lists:
len(li) - next((i for i, e in enumerate(reversed(li)) if e == "a"), len(li) + 1)
If not found, the expression evaluates to -1
.
No new list construction, 100% on iterator.
Upvotes: -1
Reputation: 301
There's a lot of answers here, so I thought it'd be interesting to compare different approaches. I've also came up with a pretty good solution not listed here and created a PyPI package.
Let's get straight to the point, here's the code I've used for benchmarking:
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()
And the results:
===== 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
Here, the "best" case means that the searched value is rightmost, "middle" - in the middle, "worst" - leftmost. Technically, the worst case would be absence of the searched value, but handling of exceptions may mess up the measurements.
rindex
from my rindex
package is superior (even beats the built-in index
in some cases) because it's written in Cython. Use it if you need the best performance.
Comparison of pure Python functions:
We can also exclude rindex3
, rindex5
and rindex6
from the competition: they do not have advantages.
rindex2
and rindex4
are almost the same, but 4 is a little faster for "good" cases, and for the "bad" ones it's the opposite.
They're also excluded because rindex8
is faster in every case.
rindex1
is probably the best one for small lists (though, rindex8
and my_rindex
are only a bit slower).
For other cases rindex8
is the fastest (even comparable to the built-in index
in the "big worst" case).
my_rindex
is almost as fast, but slows down dramatically in the "big best" case.
It supports boundaries, though.
However, if you use boundaries that effectively reduce a big list to a small list, rindex7
will be your friend.
Upvotes: 2
Reputation: 3857
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')
Upvotes: 0
Reputation: 799420
>>> (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
Upvotes: 9
Reputation: 2343
Love @alcalde's solution, but faced ValueError: max() arg is an empty sequence if none of the elements match the condition.
To avoid the error set default=None:
max((loc for loc, val in enumerate(li) if val == 'a'), default=None)
Upvotes: 1
Reputation: 34235
If the list is small, you can compute all indices and return the largest:
index = max(i for i, x in enumerate(elements) if x == 'foo')
Upvotes: 0
Reputation: 1682
lastIndexOf = lambda array, item: len(array) - (array[::-1].index(item)) - 1
Upvotes: 0
Reputation: 294516
dict
You can use the fact that dictionary keys are unique and when building one with tuples only the last assignment of a value for a particular key will be used. As stated in other answers, this is fine for small lists but it creates a dictionary for all unique values and might not be efficient for large lists.
dict(map(reversed, enumerate(li)))["a"]
6
Upvotes: 5
Reputation: 151157
I like both wim's and Ignacio's answers. However, I think itertools
provides a slightly more readable alternative, lambda notwithstanding. (For Python 3; for Python 2, use xrange
instead of range
).
>>> from itertools import dropwhile
>>> l = list('apples')
>>> l.index('p')
1
>>> next(dropwhile(lambda x: l[x] != 'p', reversed(range(len(l)))))
2
This will raise a StopIteration
exception if the item isn't found; you could catch that and raise a ValueError
instead, to make this behave just like index
.
Defined as a function, avoiding the lambda
shortcut:
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")
It works for non-chars too. Tested:
>>> rindex(['apples', 'oranges', 'bananas', 'apples'], 'apples')
3
Upvotes: 6
Reputation: 129
last_occurence=len(yourlist)-yourlist[::-1].index(element)-1
just easy as that.no need to import or create a function.
Upvotes: 7
Reputation: 4630
Here's a little one-liner for obtaining the last index, using enumerate
and a list comprehension:
li = ["a", "b", "a", "c", "x", "d", "a", "6"]
[l[0] for l in enumerate(li) if l[1] == "a"][-1]
Upvotes: -1
Reputation: 1
val = [1,2,2,2,2,2,4,5].
If you need to find last occurence of 2
last_occurence = (len(val) -1) - list(reversed(val)).index(2)
Upvotes: -1
Reputation: 4822
I came here hoping to find someone had already done the work of writing the most efficient version of list.rindex
, which provided the full interface of list.index
(including optional start
and stop
parameters). I didn't find that in the answers to this question, or here, or here, or here. So I put this together myself... making use of suggestions from other answers to this and the other questions.
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)
The technique using len(seq) - 1 - next(i for i,v in enumerate(reversed(seq)) if v == value)
, suggested in several other answers, can be more space-efficient: it needn't create a reversed copy of the full list. But in my (offhand, casual) testing, it's about 50% slower.
Upvotes: 2
Reputation: 23002
Use a simple loop:
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))
Upvotes: 1
Reputation: 788
Many of the other solutions require iterating over the entire list. This does not.
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: In hindsight this seems like unnecessary wizardry. I'd do something like this instead:
def find_last(lst, sought_elt):
for r_idx, elt in enumerate(reversed(lst)):
if elt == sought_elt:
return len(lst) - 1 - r_idx
Upvotes: 22
Reputation: 1357
A one-liner that's like Ignacio's except a little simpler/clearer would be
max(loc for loc, val in enumerate(li) if val == 'a')
It seems very clear and Pythonic to me: you're looking for the highest index that contains a matching value. No nexts, lambdas, reverseds or itertools required.
Upvotes: 61