4

Possible Duplicate:
Python: find first element in a sequence that matches a predicate

Is there a higher order function in Python standard library that encapsulates the following control flow pattern?

>>> def find(pred, coll):
...   for x in coll:
...     if pred(x):
...       return x
... 
>>> find(lambda n : n % 2 == 0, [3, 5, 8, 9, 6])
8
>>> find(lambda n : n % 2 == 0, [3, 5, 7, 9, 6])
6
>>> find(lambda n : n % 2 == 0, [3, 5, 7, 9, 1])
Community
  • 1
  • 1
missingfaktor
  • 88,931
  • 61
  • 278
  • 362
  • All of the answers were helpful to me, but since @ThiefMaster answered first, I am putting the green tick on his answer. :) I have upvoted all the answers. Thanks everyone. – missingfaktor Oct 17 '12 at 07:25

4 Answers4

11

You can combine ifilter and islice to get just the first matching element.

>>> list(itertools.islice(itertools.ifilter(lambda n: n % 2 == 0, lst), 1))
[8]

However, I wouldn't consider this anyhow more readable or nicer than the original code you posted. Wrapped in a function it will be much nicer though. And since next only returns one element there is no need for islice anymore:

def find(pred, iterable):
    return next(itertools.ifilter(pred, iterable), None)

It returns None if no element was found.

However, you still have the rather slow call of the predicate function every loop. Please consider using a list comprehension or generator expression instead:

>>> next((x for x in lst if x % 2 == 0), None)
8
ThiefMaster
  • 298,938
  • 77
  • 579
  • 623
4

itertools.ifilter() can do this, if you just grab the first element of the resulting iterable.

itertools.ifilter(pred, col1).next()

Similarly, so could a generator object (again, taking the first item out of the resulting generator):

(i for i in col1 if i % 2 == 0).next()

Since both of these are lazy-evaluated, you'll only evaluate as much of the input iterable as is necessary to get to the first element that satisfies the predicate. Note that if nothing matches the predicate, you'll get a StopIteration exception. You can avoid this by using the next() builtin instead:

next((i for i in col1 if i % 2 == 0), None)
Amber
  • 477,764
  • 81
  • 611
  • 541
2

I don't know of such a function off the top of my head, but you could just use a generator expression and take the first result.

x = (x for x in [3,5,8,9,6] if (lambda n: n % 2 == 0)(x))
y = x.next()

Or just

y = (x for x in [3,5,8,9,6] if (lambda n: n % 2 == 0)(x)).next()
samfrances
  • 2,785
  • 2
  • 22
  • 35
2
(x for x in coll if pred(x)).next()

Raises StopIteration if the item isn't found (which might be preferable to returning None, especially if None is a valid return value).

nneonneo
  • 162,933
  • 34
  • 285
  • 360