Find first element in a sequence that matches a predicate

PythonPredicate

Python Problem Overview


I want an idiomatic way to find the first element in a list that matches a predicate.

The current code is quite ugly:

[x for x in seq if predicate(x)][0]

I've thought about changing it to:

from itertools import dropwhile
dropwhile(lambda x: not predicate(x), seq).next()

But there must be something more elegant... And it would be nice if it returns a None value rather than raise an exception if no match is found.

I know I could just define a function like:

def get_first(predicate, seq):
    for i in seq:
        if predicate(i): return i
    return None

But it is quite tasteless to start filling the code with utility functions like this (and people will probably not notice that they are already there, so they tend to be repeated over time) if there are built ins that already provide the same.

Python Solutions


Solution 1 - Python

To find the first element in a sequence seq that matches a predicate:

next(x for x in seq if predicate(x))

Or simply:

Python 2:

next(itertools.ifilter(predicate, seq))

Python 3:

next(filter(predicate, seq))

These will raise a StopIteration exception if the predicate does not match for any element.


To return None if there is no such element:

next((x for x in seq if predicate(x)), None)

Or:

next(filter(predicate, seq), None)

Solution 2 - Python

You could use a generator expression with a default value and then next it:

next((x for x in seq if predicate(x)), None)

Although for this one-liner you need to be using Python >= 2.6.

This rather popular article further discusses this issue: Cleanest Python find-in-list function?.

Solution 3 - Python

I don't think there's anything wrong with either solutions you proposed in your question.

In my own code, I would implement it like this though:

(x for x in seq if predicate(x)).next()

The syntax with () creates a generator, which is more efficient than generating all the list at once with [].

Solution 4 - Python

J.F. Sebastian's answer is most elegant but requires python 2.6 as fortran pointed out.

For Python version < 2.6, here's the best I can come up with:

from itertools import repeat,ifilter,chain
chain(ifilter(predicate,seq),repeat(None)).next()

Alternatively if you needed a list later (list handles the StopIteration), or you needed more than just the first but still not all, you can do it with islice:

from itertools import islice,ifilter
list(islice(ifilter(predicate,seq),1))

UPDATE: Although I am personally using a predefined function called first() that catches a StopIteration and returns None, Here's a possible improvement over the above example: avoid using filter / ifilter:

from itertools import islice,chain
chain((x for x in seq if predicate(x)),repeat(None)).next()

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionfortranView Question on Stackoverflow
Solution 1 - PythonjfsView Answer on Stackoverflow
Solution 2 - PythonChewieView Answer on Stackoverflow
Solution 3 - PythonmacView Answer on Stackoverflow
Solution 4 - Pythonparity3View Answer on Stackoverflow