111

What would be the most Pythonic way to find the first index in a list that is greater than x?

For example, with

list = [0.5, 0.3, 0.9, 0.8]

The function

f(list, 0.7)

would return

2.
Oliver Ni
  • 2,606
  • 7
  • 29
  • 44
c00kiemonster
  • 22,241
  • 34
  • 95
  • 133
  • 70
    don't use 'list' as a variable name... – mshsayem Feb 10 '10 at 13:41
  • 2
    The question is ambiguous. Is the answer `2` because `0.9 > 0.7` or because `0.8 > 0.7`? In other words, are you searching sequentially or in the order of increasing values? – Sergey Orshanskiy Jun 17 '15 at 20:00
  • 2
    Possible duplicate of [What is the best way to get the first item from an iterable matching a condition?](http://stackoverflow.com/questions/2361426/what-is-the-best-way-to-get-the-first-item-from-an-iterable-matching-a-condition) – Cristian Ciupitu Jan 06 '16 at 19:07
  • I voted to close this question as a duplicate instead of doing vice-versa because the newer question is more generic. – Cristian Ciupitu Jan 06 '16 at 19:12
  • Ok so this function gets the next value in the list which is larger, but does not get the NEXT Largest Value ? That would be 0.8 so Index 3, How would that be achieved ? – Rudy Van Drie May 15 '20 at 11:17

12 Answers12

151
next(x[0] for x in enumerate(L) if x[1] > 0.7)
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • 35
    +1: Although I would prefer to avoid the magic numbers: next(idx for idx, value in enumerate(L) if value > 0.7) – mthurlin Feb 10 '10 at 13:12
  • 46
    +1 for simplicity and `next()`, but maybe this for readability: `next(i for i,v in enumerate(L) if v > 0.7)` – Will Hardy Feb 10 '10 at 13:14
  • 20
    While this is nice looking, the case where there's no result will raise a confusing StopIteration. – Virgil Dupras Feb 10 '10 at 13:15
  • 1
    Correct me if I'm wrong here, but the next() method will basically just keep on asking for the next item until it has found a match, right? So all I got to do is to catch a possible StopIteration as Virgil points out. – c00kiemonster Feb 10 '10 at 13:18
  • 2
    @c00kiemonster no, next() just gets the first item of the iterator. But the iterator is filtered, so the first item is the right item. – Virgil Dupras Feb 10 '10 at 13:19
  • why doesn't my python have next? is there something I need to import? – flybywire Feb 10 '10 at 13:34
  • 2
    @flybywire: `next()` is in 2.6+. Call the `next()` method of the genex in earlier versions. – Ignacio Vazquez-Abrams Feb 10 '10 at 13:38
  • To avoid the StopIteration problem and return -1 instead, you can use ([i for i,v in enumerate(L) if v > 0.7] + [-1])[0] – Wim Feb 10 '10 at 20:16
  • 3
    @Wim: But then you regress back to evaluating the entire sequence. Use `itertools.chain()` instead of adding lists like this. – Ignacio Vazquez-Abrams Feb 10 '10 at 20:18
  • 1
    Oh right, I didn't get the quick exit part yet... `next(itertools.chain(iter(i for i,v in enumerate(L) if v > 0.7), [-1]))` then? – Wim Feb 10 '10 at 23:29
  • 7
    @Wim you don't need chain() here. next() accepts the second argument: `next((i for i, x in enumerate(L) if x > value), -1)` – jfs Jan 11 '18 at 08:30
56

if list is sorted then bisect.bisect_left(alist, value) is faster for a large list than next(i for i, x in enumerate(alist) if x >= value).

ClimateUnboxed
  • 7,106
  • 3
  • 41
  • 86
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • Nice answer - It was definitely 5 to 6 times faster for me using a small 4 element sorted list but, (not sure if I'm making an error), but when I time this using timeit with a long list of 10000 element numpy array I find it is roughly twice as slow as the list comprehension answer above though, which I was surprised about. – ClimateUnboxed Mar 22 '20 at 23:15
  • 2
    @AdrianTompkins: something is wrong with your benchmark. `bisect_left` is O(log n), while listcomp is O(n) i.e., the larger the `n`, the more advantage on the `bisect_left()` side. I've tried to find index of `500_000` in `range(10**6)` using `bisect_left()` -> 3.75 microseconds and using the genexpr with `next()`-> 51.0 milliseconds [`10_000` times] slower as expected. – jfs Mar 23 '20 at 15:05
  • As is so often the case with SO, it's a bit hard to understand why this is not marked as the solution. Of course, there was an earlier answer by a few hours, but @c00kiemonster, if you're still alive and Pythoning, wake up and have a look! 12 years later, this is still better than the one above. – Ricardo Nov 04 '22 at 21:19
  • how to proceed if my list is of type `Class`? – ignacio Nov 11 '22 at 20:22
21
>>> alist= [0.5, 0.3, 0.9, 0.8]
>>> [ n for n,i in enumerate(alist) if i>0.7 ][0]
2
S.Lott
  • 384,516
  • 81
  • 508
  • 779
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
  • 2
    it will fail if 'x' is greater than any other value in the list – mshsayem Feb 10 '10 at 13:46
  • 2
    @mshsayem: Problem is ill-defined for this case. Failure may be the right thing to do. – S.Lott Feb 10 '10 at 14:43
  • @S.Loot: Good point. Failing if not in the list results in an understandable error when assigning this to a variable: `IndexError: list index out of range`. Using `index = next[ n for n,i in enumerate(alist) if i>0.7 ]` error gives: `NameError: name 'index' is not defined`.`next` is slightly faster: Timing difference is 12.7 ns versus 11.9 ns for 60 000 numbers. – Leo Nov 05 '20 at 23:44
16
filter(lambda x: x>.7, seq)[0]
flybywire
  • 261,858
  • 191
  • 397
  • 503
  • 4
    -1: While technically correct, dont use filter where a list comprehension is both more readable and more performant – mthurlin Feb 10 '10 at 13:18
  • filter(lambda x: x[1] > .7, enumerate(seq))[0][0] - simple linear search – lowtech Oct 16 '13 at 16:13
  • 5
    @truppo filter in python 3 returns a generator, so it should be no worse than a list comprehension? Also I find this way more readable than the enumerate solution. – BubuIIC Nov 30 '13 at 01:02
  • One thing that isn't nice about this one is that you get into exception handling if there is no item in the seq larger than .7. – Brian C. Nov 15 '18 at 15:30
  • The solution is technically incorrect. The question author asked how to find *index* of element in the list. But this solution return a record instead. Eventmore in Python 3.8 it's slower that `bisect_left()` (the fastest) and `enumerate()`. – Sergey Nevmerzhitsky Feb 26 '20 at 12:34
11

This answer performs a simple search over the list using enumerate to track the position in the list and breaking out of the loop when the first value greater than the threshold is found. If no entry meets the criterion, an error is raised.

for index, elem in enumerate(elements):
    if elem > reference:
        return index
raise ValueError("Nothing Found")
ClimateUnboxed
  • 7,106
  • 3
  • 41
  • 86
Virgil Dupras
  • 2,634
  • 20
  • 22
9

1) NUMPY ARGWHERE, general lists

If you are happy to use numpy (imported as np here), then the following will work on general lists (sorted or unsorted):

np.argwhere(np.array(searchlist)>x)[0]

or if you need the answer as an integer index:

np.argwhere(np.array(searchlist)>x)[0][0]

2) NUMPY SEARCHSORTED, sorted lists (very efficient for searching lists within a list)

However, if your search list [l1,l2,...] is sorted, it is much cleaner and nicer to use the function np.searchsorted:

np.searchsorted(searchlist,x)

The nice thing about using this function is that as well as searching for a single value x within the search list [l1,l2,...], you can also search for a list of values [x1,x2,x3...xn] within your search list (i.e. x can be a list too, and it is extremely efficient relative to a list comprehension in this case).

ClimateUnboxed
  • 7,106
  • 3
  • 41
  • 86
5

Another one:

map(lambda x: x>.7, seq).index(True)
flybywire
  • 261,858
  • 191
  • 397
  • 503
4

I know there are already plenty of answers, but I sometimes I feel that the word pythonic is translated into 'one-liner'.

When I think a better definition is closer to this answer:

"Exploiting the features of the Python language to produce code that is clear, concise and maintainable."

While some of the above answers are concise, I do not find them to be clear and it would take a newbie programmer a while to understand, therefore not making them extremely maintainable for a team built of many skill levels.

l = [0.5, 0.3, 0.9, 0.8]

def f(l, x):
    for i in l:
        if i >x: break
    return l.index(i)


f(l,.7)

or

l = [0.5, 0.3, 0.9, 0.8]

def f(l, x):
    for i in l:
        if i >x: return l.index(i)



f(l,.7)

I think the above is easily understood by a newbie and is still concise enough to be accepted by any veteran python programmer.

I think writing dumb code is a positive.

Jeff Tilton
  • 1,256
  • 1
  • 14
  • 28
  • I agree with your comment about clear code, but the use of loops would make this extremely slow if needed multiple times in a large code – ClimateUnboxed May 03 '22 at 07:23
2

I had similar problem when my list was very long. Comprehension or filter-based solutions would go through the whole list. Instead itertools.takewhile will break the loop once the condition is false for the first time:

from itertools import takewhile

def f(l, b): return len([x for x in takewhile(lambda x: x[1] <= b, enumerate(l))])

l = [0.5, 0.3, 0.9, 0.8]
f(l, 0.7)
ClimateUnboxed
  • 7,106
  • 3
  • 41
  • 86
lowtech
  • 2,492
  • 2
  • 22
  • 31
2
>>> f=lambda seq, m: [ii for ii in xrange(0, len(seq)) if seq[ii] > m][0]
>>> f([.5, .3, .9, .8], 0.7)
2
flybywire
  • 261,858
  • 191
  • 397
  • 503
  • That looks pretty slick. But theoretically it will traverse the whole list and then return the first result (greater than x), right? Is there any way to make one that stops straight after finding the first result? – c00kiemonster Feb 10 '10 at 13:08
  • what's wrong with traversing whole list? if the first value greater than 0.7 is near the end of the list, it doesn't make a difference. – ghostdog74 Feb 10 '10 at 13:12
  • 3
    True. But in this particular case the lists I intend to use the function on are pretty long, so I'd prefer it to quit traversing as soon as a match is found... – c00kiemonster Feb 10 '10 at 13:15
  • whether its long or not, if the first value is the last second item of the list, you will still have to traverse the whole list to get there! – ghostdog74 Feb 10 '10 at 13:18
  • 4
    @ghostdog74: Yes, but this is not a reason to want all cases to be worst cases. – UncleBens Feb 10 '10 at 16:26
1

You could also do this using numpy:

import numpy as np

list(np.array(SearchList) > x).index(True)
Szymon Maszke
  • 22,747
  • 4
  • 43
  • 83
-1

Try this one:

def Renumerate(l):
    return [(len(l) - x, y) for x,y in enumerate(l)]

example code:

Renumerate(range(10))

output:

(10, 0)
(9, 1)
(8, 2)
(7, 3)
(6, 4)
(5, 5)
(4, 6)
(3, 7)
(2, 8)
(1, 9)
B. Go
  • 1,436
  • 4
  • 15
  • 22
Navid
  • 1