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
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')
next
and pass the default value (empty list in this case probably)?
Commented
Oct 2, 2013 at 9:56
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
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
StopIteration
if generator describes empty sequence.
Commented
Dec 8, 2014 at 16:04
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.
zip
, map
, and filter
all become lazy in Python3. (Replacements for Python2's imap
, izip
, and ifilter
.)
Commented
May 18, 2011 at 3:26
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.
Commented
Apr 2, 2018 at 9:52
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]
Too lazy to write:
mylist = [10, 2, 20, 5, 50]
max(mylist, key=lambda x: x>10)
[i for i in mylist if i > 10]