98

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?

Stefan van den Akker
  • 6,242
  • 7
  • 43
  • 62
Shaokan
  • 6,932
  • 12
  • 55
  • 79

16 Answers16

114

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 large, efficiency can be better with a lazy approach:

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')
wim
  • 302,178
  • 90
  • 548
  • 690
  • 2
    YMMV, but for this example, `len(li) - next(i for i, v in enumerate(reversed(li), 1) if v == 'a')` is a little faster for me – John La Rooy Feb 28 '17 at 04:37
  • 18
    there is `str.rindex()`, any reason there isn't `list.rindex()`? – Chris_Rands Sep 25 '17 at 14:16
  • 2
    @Chris_Rands: Better than `range(len(li)-1, -1, -1)` is either `reversed(range(len(li)))` or `range(len(li))[::-1]` (they're roughly equivalent too, unlike most comparisons between `reversed` and the reversing slice; modern Py3 `range` can be sliced in reverse to make another lazy `range` that runs backwards, so it's equally performant). Looks like wim went with the former. – ShadowRanger Sep 26 '19 at 16:58
  • @ShadowRanger yes, wim edited his answer since i wrote that comment. this was the answer at the time https://stackoverflow.com/revisions/48105b58-6fe2-4b80-bb3c-e3960b7fff56/view-source I will delete my earlier comment (and this one) to avoid confusion – Chris_Rands Sep 26 '19 at 19:55
  • Pythonic version `next(len(a)-i-1 for i, x in enumerate(reversed(a)) if x == 3)` – coder.in.me Nov 02 '20 at 11:35
51

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.

alcalde
  • 1,199
  • 12
  • 10
  • 7
    As @Isaac points out this always iterates over all N elements of li. – smci Dec 14 '14 at 12:44
  • 1
    This is perfect. Maybe not ideal for a large data set but for small amount of data its great. – srock Jun 03 '16 at 21:10
  • a significant downside is the fact you will always iterate through the whole list. you should emphasis that in your answer. – Guy Oct 10 '18 at 08:47
  • 1
    Use **default=None** to avoid error "ValueError: max() arg is an empty sequence" ```max((loc for loc, val in enumerate(li) if val == 'a'), default=None)``` – Ilarion Halushka Feb 01 '22 at 11:08
18

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
Isaac
  • 728
  • 5
  • 15
8
>>> (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
Jules G.M.
  • 3,319
  • 1
  • 19
  • 33
Ignacio Vazquez-Abrams
  • 740,318
  • 145
  • 1,296
  • 1,325
  • 2
    Why not `reversed(enumerate(li))`? – Isaac Apr 18 '14 at 01:44
  • 40
    Because it didn't occur to me 3 years ago. – Ignacio Vazquez-Abrams Apr 18 '14 at 01:49
  • Weirdly though, `reversed(enumerate(li))` results in an error that reads `argument to reversed() must be a sequence`! And it says that for `reversed((y for y in enumarete(li))` too! – trss Aug 25 '14 at 14:50
  • 2
    Makes sense that `reversed()` cannot operate on iterators in general, including generators. So, `enumerate(reversed(li))` and adjusting the index component of the enumerated tuples is a workaround that avoids creating a copy of the list. – trss Aug 25 '14 at 15:27
7

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
senderle
  • 136,589
  • 35
  • 205
  • 230
  • Python 3 now permits a default arg to `next`, so you can eliminate the `try... except` – PM 2Ring Jun 09 '18 at 03:09
  • @PM2Ring, I don't know why you'd want to use a default arg here though. Then you'd need to have an `if` statement and raise a `ValueError` inside that. (Recall that we're trying to duplicate the `index` API exactly, which means raising a `ValueError` if the item can't be found.) Using `try` in this context is more idiomatic and, I would guess, more efficient. – senderle Jun 09 '18 at 19:21
  • Fair call. Maybe raise `IndexError` rather than `ValueError`, to be consistent with `.index` ? – PM 2Ring Jun 11 '18 at 16:35
  • `IndexError` is for passing an incorrect index. Passing a missing value to `index` generates a `ValueError`. – senderle Jun 11 '18 at 16:41
5

With 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
piRSquared
  • 265,629
  • 48
  • 427
  • 571
2

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.

dubiousjim
  • 4,604
  • 1
  • 34
  • 32
2
last_occurence=len(yourlist)-yourlist[::-1].index(element)-1

just easy as that.no need to import or create a function.

Prabu M
  • 79
  • 1
  • 8
1
lastIndexOf = lambda array, item: len(array) - (array[::-1].index(item)) - 1
Sapphire_Brick
  • 1,402
  • 9
  • 23
1

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)
Ilarion Halushka
  • 1,685
  • 15
  • 11
0

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))
Laurent LAPORTE
  • 20,141
  • 5
  • 53
  • 92
0

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')
danijar
  • 30,040
  • 39
  • 151
  • 272
0

Here is a function for finding the last occurrence of an element in a list. A list and an element are passed to the function.

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

Inside the last_occurrence function a for loop is used with range. which will iterate the list in reverse order. if the element of the current index match the searched element, the function will return the index. In case after comparing all elements in the list the searched element is not found function will return -1.

Rubel
  • 926
  • 11
  • 17
-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))
user2426679
  • 2,647
  • 1
  • 29
  • 29
-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)

Jeru Luke
  • 16,909
  • 11
  • 66
  • 79
-1

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]
quazgar
  • 4,029
  • 2
  • 28
  • 39