Question

What is the Pythonic way to check if a iterables's length is at least n?

This is my approach:

import itertools

def is_iterable_longer_than(iterable, n):
    return n <= len(itertools.islice(iterable, n))

Is there anything better?

EDIT:

I am willing to consume the iterable, even if it it can be evaluated only once.

As pointed out, the above has an error. It should be:

    return n <= len(list(itertools.islice(iterable, n)))
Was it helpful?

Solution

There's no general way. Yours doesn't even work:

>>> def is_iterable_longer_than(iterable, n):
...     return n <= len(itertools.islice(iterable, n))
...
>>> is_iterable_longer_than([], 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in is_iterable_longer_than
TypeError: object of type 'itertools.islice' has no len()

The only way to tell if an iterable has at least n objects in it is to iterate over it until you get n objects or run out. Unfortunately, some iterables can only be iterated over once. If you don't care about using the contents of the iterable, you can do this:

def is_iterable_longer_than(iterable, n):
    return n == sum(1 for _ in itertools.islice(iterable, n))

If you need to use the contents of the iterable, you can create another iterable that looks like the original:

def is_iterable_longer_than(iterable, n):
    iter1, iter2 = itertools.tee(iterable)
    return sum(1 for _ in itertools.islice(iter1, n)) == n, iter2

While we're at it, we might as well try len, just in case it works:

def is_iterable_longer_than(iterable, n):
    iter1, iter2 = itertools.tee(iterable)
    try:
        return len(iterable) >= n, iter2
    except TypeError:
        return sum(1 for _ in itertools.islice(iter1, n)) == n, iter2

OTHER TIPS

It depends on whether you're willing to evaluate the generator or not.

If you are, it's trivial:

def gen_length_was_at_least_n(gen, n):
    return n == sum(1 for _ in itertools.islice(gen, n))

If you're not, then you're stuck, unless you're willing to evaluate it but keep the values buffered via tee:

gen, extra = itertools.tee(gen)
if gen_length_was_at_least_n(extra):
    # ... do something with gen

Note that this does partially evaluate the original iterator; it just keeps the values around and serves them through the new generator returned from tee. That means if evaluating the generator has side effects, they'll be triggered when you do the length check.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top