5

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
  • 90,905
  • 62
  • 285
  • 365
  • 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

12

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
  • 310,957
  • 84
  • 592
  • 636
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
  • 507,862
  • 82
  • 626
  • 550
  • I hope this doesn't compute the whole collection prematurely? I need only the first element that meets the criterion. – missingfaktor Oct 17 '12 at 07:14
  • See the note I just edited in as you were writing your comment - both of the methods I mentioned are lazily evaluated. – Amber Oct 17 '12 at 07:14
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
  • 3,405
  • 3
  • 25
  • 40
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
  • 171,345
  • 36
  • 312
  • 383