33

How do I find an object in a sequence satisfying a particular criterion?

List comprehension and filter go through the entire list. Is the only alternative a handmade loop?

mylist = [10, 2, 20, 5, 50]
find(mylist, lambda x:x>10) # Returns 20
3
  • You mean you just want to find the first object matching the criteria?
    – Blair
    Commented May 18, 2011 at 3:08
  • for more than one object you should do [i for i in mylist if i > 10]
    – JBernardo
    Commented May 18, 2011 at 3:11
  • @Blair and @JBernardo, yes, only the first matching object.
    – Salil
    Commented May 18, 2011 at 3:18

4 Answers 4

40

Here's the pattern I use:

mylist = [10, 2, 20, 5, 50]
found = next(i for i in mylist if predicate(i))

Or, in Python 2.4/2.5, next() is a not a builtin:

found = (i for i in mylist if predicate(i)).next()

Do note that next() raises StopIteration if no element was found. In most cases, that's probably good. You asked for the first element, no such element exists, and so the program probably cannot continue.

If, on the other hand, you do know what to do in that case, you can supply a default to next():

conf_files = ['~/.foorc', '/etc/foorc']
conf_file = next((f for f in conf_files if os.path.exists(f)),
                 '/usr/lib/share/foo.defaults')
4
  • Have you come out with it yourself or have you seen it somewhere and if so where? Also, wouldn't it be better to use built-in next and pass the default value (empty list in this case probably)? Commented Oct 2, 2013 at 9:56
  • 1
    next() was introduced in Python 2.6 already - docs.python.org/2.6/library/functions.html?highlight=next#next Commented Oct 2, 2013 at 19:55
  • @Piotr: I think that may depend on the particular use case. If you want the first element of an empty sequence, there is no correct value to return, it has no first element. It might make perfectly good sense to raise an exception. bool(foo(next(filter(foo, []), [])) != True, that is, unless foo is something like lambda x: x == [], and [] is certainly not in [], so it's a lie in any case. Commented Oct 2, 2013 at 22:31
  • Your code throws an exception StopIteration if generator describes empty sequence.
    – Alex G.P.
    Commented Dec 8, 2014 at 16:04
15

Actually, in Python 3, at least, filter doesn't go through the entire list.

To double check:

def test_it(x):
    print(x)
    return x>10

var = next(filter(test_it, range(20)))

In Python 3.2, that prints out 0-11, and assigns var to 11.

In 2.x versions of Python you may need to use itertools.ifilter.

2
  • Good call; zip, map, and filter all become lazy in Python3. (Replacements for Python2's imap, izip, and ifilter.) Commented May 18, 2011 at 3:26
  • I prefer this to using for as it is much clearer. Note 1: this will also throw StopIteration if no match. Note 2: if your list is sorted, consider using bisect if appropriate.
    – Jonathan H
    Commented Apr 2, 2018 at 9:52
7

If you only want the first greater than 10 you can use itertools.ifilter:

import itertools
first_gt10 = itertools.ifilter(lambda x: x>10, [10, 2, 20, 5, 50]).next()

If you want all greater than 10, it may be simplest to use a list-comprehension:

all_gt10 = [i for i in mylist if i > 10]
-1

Too lazy to write:

mylist = [10, 2, 20, 5, 50]
max(mylist, key=lambda x: x>10)
1
  • 6
    Too lazy to write to write what? Commented Jul 27, 2018 at 7:27

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