views:

280

answers:

3

I'm wondering if there's a reason that there's no first(iterable) in the Python built-in functions, somewhat similar to any(iterable) and all(iterable) (it may be tucked in a stdlib module somewhere, but I don't see it in itertools). first would perform a short-circuit generator evaluation so that unnecessary (and a potentially infinite number of) operations can be avoided; i.e.

def identity(item):
    return item

def first(iterable, predicate=identity):
    for item in iterable:
        if predicate(item):
            return item
    raise ValueError('No satisfactory value found')

This way you can express things like:

denominators = (2, 3, 4, 5)
lcd = first(i for i in itertools.count(1)
    if all(i % denominators == 0 for denominator in denominators))

Clearly you can't do list(generator)[0] in that case, since the generator doesn't terminate.

Or if you have a bunch of regexes to match against (useful when they all have the same groupdict interface):

match = first(regex.match(big_text) for regex in regexes)

You save a lot of unnecessary processing by avoiding list(generator)[0] and short-circuiting on a positive match.

+8  A: 

If you have an iterable, you can just call its next method. Something like:

In [3]: (5*x for x in xrange(2,4)).next()
Out[3]: 10
liori
D'oh, of course! In Py3k the builtin is `next(iterator)`.
cdleary
+2  A: 

Haskell makes use of what you just described, as the function take (or as the partial function take 1, technically). Python Cookbook has generator-wrappers written that perform the same functionality as take, takeWhile, and drop in Haskell.

But as to why that's not a built-in, your guess is as good as mine.

Mark Rushakoff
Alex Martelli
wouldn't the Haskell equivalent of _first_ be _head_? "take 1" returns a list, not an element.
tokland
+2  A: 

There's some ambiguity in your question. Your definition of first and the regex example imply that there is a boolean test. But the denominators example explicitly has an if clause; so it's only a coincidence that each integer happens to be true.

It looks like the combination of next and itertools.ifilter will give you what you want.

match = next(itertools.ifilter(None, (regex.match(big_text) for regex in regexes)))
Coady
True, if the answer were zero we'd have a problem. The `next(iterator)` was the answer I was missing.
cdleary