3

I want to iterate over elements from the end of a list to find the index of the last occurrence of an element which contains a '}'.

Ideally, I want to maintain efficiency using iterators rather than actually reversing the whole list or searching for all occurrences that match this condition.

If I were finding the first occurrence, I would use:

next(i for i,x in enumerate(list) if '}' in x)

However, I tried something similar using reversed to create a reverse iterator, but this did not seem to work, presumably because enumerate doesn't handle iterators, or maybe I have misunderstood...

next(i for i,x in enumerate(reversed(list)) if '}' in x)

I am aware other ways of doing this would be:

[i for i,x in enumerate(list) if '}' in x][-1]

or:

next(i for i,x in enumerate(list[::-1]) if '}' in x)

However, I am looking for the most efficient way. I could of course write a loop for efficiency, but was wondering if there was a neat way of doing it using the functions available.

I am relatively new to Python so go easy on me, and let me know if I have misunderstood anything. I am using Python 2.7.

Thanks in advance.

8
  • is this list a list of characters? Commented Jul 24, 2015 at 16:36
  • If you're looking for efficiency, don't use Python. Use C/C++/Java. Good luck!
    – Nir Alfasi
    Commented Jul 24, 2015 at 16:37
  • Agreed, but lets go from the assumption that I have to use Python for legacy reasons. Commented Jul 24, 2015 at 16:38
  • 1
    what's wrong with your last solution? like @alfasin said, if you're that worried about efficiency python is not what you're looking for Commented Jul 24, 2015 at 16:43
  • 1
    enumerate should totally work for any random iterator... Commented Jul 24, 2015 at 16:43

5 Answers 5

2
def rfind(lst, condition):
    return next((len(lst) - n
                 for (n, i) in enumerate(reversed(lst), 1)
                 if condition(i)),
                -1) # If we couldn't find anything, return -1

>>> lst = ['{b}', '[c]', '{a}', 'g']
>>> print rfind(lst, lambda i: '{' in i)
2
>>> print rfind(lst, lambda i: 'q' in i)
-1
3
  • 1
    Nice & modular, but it does incur the cost of an extra function call for every item tested.
    – PM 2Ring
    Commented Jul 24, 2015 at 17:01
  • 1
    @PM2Ring This is true. It depends how often OP is calling it and the level at which speed is preferred above readability. Commented Jul 24, 2015 at 17:07
  • 1
    Ah! Now I see the merit of putting the arithmetic in the gen exp, since it makes handling the situation where we don't get a match more elegant. :)
    – PM 2Ring
    Commented Jul 24, 2015 at 17:08
2

Your example

next(i for i,x in enumerate(reversed(lst)) if '}' in x)

works, but it returns the index of the reversed list. So if it finds the '}' in the last string of the (unreversed) list it will return 0.

So to get the index in the original, unreversed list you could do

len(lst) - 1 - next(i for i,x in enumerate(reversed(lst)) if '}' in x)

The above code raises StopIteration if '}' isn't found in lst. You can avoid that by doing

len(lst) - 1 - next((i for i,x in enumerate(reversed(lst)) if '}' in x), len(lst))

and (like Kupiakos's code) it will return -1 if '}' isn't found.


BTW, don't use list as a variable name (even in example code) since it shadows the built-in list type.

4
  • Why not do length = len(lst); next((length - i - 1) for i,x in enumerate(reversed(lst)) if '}' in x)? Commented Jul 24, 2015 at 16:49
  • @Kupiakos: Sure, although I'd probably put the arithmetic outside the generator expression. I guess I should add something like that to my answer.
    – PM 2Ring
    Commented Jul 24, 2015 at 16:53
  • Not sure if there's any advantage in providing len(lst) twice. If the item can't be found, it will be evaluated twice. Commented Jul 24, 2015 at 18:56
  • @Kupiakos: Hey, I said your way was more elegant (and I gave you an upvote). :) But len() is very fast on Python's built-in container types, since they all keep track of their current length in a simple attribute.
    – PM 2Ring
    Commented Jul 25, 2015 at 11:28
0

Code for finding max index of item in list:

mylist = [10, 1, 2, 3, 10, 3]
max_index=max([i for i, j in enumerate(mylist) if j==10])
max_index

Output: 4

You can see that the index of 10 is at four, python indexes start at zero. Cheers.

1
  • 1
    This is probably not as efficient as what the OP would like.
    – PM 2Ring
    Commented Jul 24, 2015 at 16:58
0

Using itertools.ifilter() should be efficient:

from itertools import ifilter

def rsearch(lst, condition):
    try:
        return (len(lst) - next(ifilter(lambda t: condition(t[1]),
                                        enumerate(reversed(lst), 1)))[0])
    except StopIteration:
        return None  # nothing satisfied condition

lst = ['{b}', '[c]', '{a}', 'g']

print(rsearch(lst, lambda s: '}' in s))  # -> 2
3
  • I'm not sure why this would be any more efficient than a generator comprehension. Not to mention that setting up try and except take unneeded processing. Commented Jul 24, 2015 at 17:42
  • @Kupiakos: It's more efficient because ifilter() is written in C (see the source code). Also the overhead for a single try/except in Python is minimal.
    – martineau
    Commented Jul 24, 2015 at 17:58
  • Timed in v2.7 and it is pretty slow compared to other solutions.
    – wwii
    Commented Jul 24, 2015 at 19:21
0

This seems to be comparable in time to a generator expression.

a = map(str, xrange(1000000))
a[2000] = 'a'

def f(a):
    for i,n in enumerate(reversed(a)):
        if 'a' in n:
            return len(a) - i - 1

Not the answer you're looking for? Browse other questions tagged or ask your own question.