views:

137

answers:

6

In Python, I would like to get the first item from a list matching a condition. For example, the following function is adequate:

def first(the_iterable, condition = lambda x: True):
    for i in the_iterable:
        if condition(i):
            return i

This function could be used something like this:

>>> first(range(10))
0
>>> first(range(10), lambda i: i > 3)
4

However, I can't think of a good built-in / one-liner to let me do this (and I don't particularly want to copy this function around if I don't have to). Any ideas?

(It's important that the resulting method not process the entire list, which could be quite large.)

+1  A: 

The itertools module contains a filter function for iterators. The first element of the filtered iterator can be obtained by calling next() on it:

from itertools import ifilter

print ifilter((lambda i: i > 3), range(10)).next()
sth
Generator expressions are simpler.
EOL
(`i`)`filter` and (`i`)`map` can make sense for cases where the functions being applied already exist, but in a situation like this it makes a lot more sense just to use a generator expression.
Mike Graham
+5  A: 

Similar to using ifilter, you could use a generator expression:

>>> (x for x in xrange(10) if x > 5).next()
6

In either case, you probably want to catch StopIteration though, in case no elements satisfy your condition.

Technically speaking, I suppose you could do something like this:

>>> foo = None
>>> for foo in (x for x in xrange(10) if x > 5): break
... 
>>> foo
6

It would avoid having to make a try/except block. But that seems kind of obscure and abusive to the syntax.

Matt Anderson
+1: Not obscure, nor abusive. All things considered, the last one seems pretty clean.
S.Lott
The last one isn't at all clean—`for foo in genex: break` is just a way of doing `foo = next(genex)` without making the assignment clear and with the exception that would be raised if the operation doesn't make sense being squashed. Ending up with a failure code instead of catching an exception is usually a *bad* thing in Python.
Mike Graham
+1  A: 

I would write this

next(x for x in xrange(10) if i > 3)
Mike Graham
A: 

Oneliner:

thefirst = [i for i in range(10) if i > 3][0]

If youre not sure that any element will be valid according to the criteria, you should enclose this with try/except since that [0] can raise an IndexError.

mizipzor
TypeError: 'generator' object is unsubscriptable
jleedev
My bad, should be list comprehension not a generator, fixed... thanks! :)
mizipzor
There is no reason to evaluate the whole iterable (which may not be possible). It is more robust and efficient to use one of the other solutions provided.
Mike Graham
+1  A: 

For older versions of Python where the next built-in doesn't exist:

(x for x in range(10) if x > 3).next()
Menno Smits
+3  A: 

In Python 2.6 or better, next(x for x in the_iterable if x > 3), assuming you want None if no item in the iterale satisfies the condition (if you want a different default result in that case, you can pass it to next as a second argument, but you'll need an extra pair of parentheses around the generator expression).

I see most answers resolutely ignore the next built-in and so I assume that for some mysterious reason they're 100% focused on versions 2.5 and older -- without mentioning the Python-version issue (but then I don't see that mention in the answers that do mention the next built-in, which is why I thought it necessary to provide an answer myself -- at least the "correct version" issue gets on record this way;-).

In 2.5, the .next() method of iterators immediately raises StopIteration if the iterator immediately finishes -- i.e., for your use case, if no item in the iterable satisfies the condition. If you don't care (i.e., you know there must be at least one satisfactory item) then just use .next() (best on a genexp, line for the next built-in in Python 2.6 and better).

If you do care, wrapping things in a function as you had first indicated in your Q seems best, and while the function implementation you proposed is just fine, you could alternatively use itertools, a for...: break loop, or a genexp, or a try/except StopIteration as the function's body, as various answers suggested. There's not much added value in any of these alternatives so I'd go for the starkly-simple version you first proposed.

Alex Martelli