Какой самый короткий способ подсчитать количество элементов в генераторе / итераторе? - PullRequest
59 голосов
/ 22 марта 2011

Если я хочу, чтобы количество элементов в итерируемом элементе не заботилось о самих элементах, каков был бы питонный способ получить это? Прямо сейчас я бы определил

def ilen(it):
    return sum(itertools.imap(lambda _: 1, it))    # or just map in Python 3

но я понимаю, lambda близок к тому, чтобы считаться вредным, а lambda _: 1 определенно не хорош.

(Вариант использования этого - подсчет количества строк в текстовом файле, соответствующих регулярному выражению, т.е. grep -c.)

Ответы [ 6 ]

128 голосов
/ 22 марта 2011

Обычный способ -

sum(1 for i in it)
26 голосов
/ 22 декабря 2015

Метод, который значительно быстрее, чем sum(1 for i in it), когда итерация может быть длинной (и не значительно медленнее, когда итерация короткая), при этом поддерживается фиксированное поведение памяти (в отличие от len(list(it))), чтобы избежать перебора и перераспределения для большихвходные данные:

# On Python 2 only, get zip that lazily generates results instead of returning list
from future_builtins import zip

from collections import deque
from itertools import count

def ilen(it):
    # Make a stateful counting iterator
    cnt = count()
    # zip it with the input iterator, then drain until input exhausted at C level
    deque(zip(it, cnt), 0) # cnt must be second zip arg to avoid advancing too far
    # Since count 0 based, the next value is the count
    return next(cnt)

Как и len(list(it)), он выполняет цикл в C-коде на CPython (deque, count и zip все реализованы на C);избегание выполнения байтового кода в цикле обычно является ключом к производительности в CPython.

Удивительно сложно придумать честные тестовые примеры для сравнения производительности (list читы с использованием __length_hint__, что вряд лидоступные для произвольных входных итераций, функции itertools, которые не предоставляют __length_hint__, часто имеют специальные режимы работы, которые работают быстрее, когда значение, возвращаемое в каждом цикле, освобождается до того, как запрошено следующее значение, что deque с maxlen=0 Сделаю).Тестовый пример, который я использовал, был для создания функции генератора, которая будет принимать входные данные и возвращать генератор уровня C, в котором не было специальных itertools возвращаемых оптимизаций контейнера или __length_hint__, используя Python 3.3 yield from:

def no_opt_iter(it):
    yield from it

Затем с помощью ipython %timeit magic (с заменой различных констант на 100):

>>> %%timeit -r5 fakeinput = (0,) * 100
... ilen(no_opt_iter(fakeinput))

Когда ввод недостаточно велик, чтобы len(list(it)) мог вызвать проблемы с памятью, в LinuxПри работе на Python 3.5 x64 мое решение занимает примерно на 50% больше, чем def ilen(it): return len(list(it)), независимо от длины ввода.

Для наименьшего из входов настройка стоит вызвать deque / zip / count / next означает, что этот путь занимает бесконечно больше времени, чем def ilen(it): sum(1 for x in it) (примерно на 200 нс на моей машине для ввода длины 0, что на 33% больше, чем при простом подходе sum), но для более длинных входов,он работает примерно вдвое меньше на каждый дополнительный элемент;для входов длины 5 стоимость эквивалентна, а где-то в диапазоне длин 50-100 начальные издержки незаметны по сравнению с реальной работой;Подход sum занимает примерно вдвое больше времени.

В основном, если использование памяти имеет значение, или входы не имеют ограниченного размера и вам важна скорость, а не краткость, используйте это решение.Если входные данные ограничены и маловаты, len(list(it)), вероятно, лучше, и если они не ограничены, но простота / краткость имеет значение, вы должны использовать sum(1 for x in it).

7 голосов
/ 22 марта 2011

Короткий путь это:

def ilen(it):
    return len(list(it))

Обратите внимание, что если вы генерируете лот элементов (скажем, десятки тысяч или более), то размещение их в списке может стать проблемой производительности. Однако это простое выражение идеи, когда производительность в большинстве случаев не имеет значения.

3 голосов
/ 31 августа 2017

more_itertools - сторонняя библиотека, в которой реализован инструмент ilen. pip install more_itertools

import more_itertools as mit


mit.ilen(x for x in range(10))
# 10
1 голос
/ 15 апреля 2016

Мне нравится пакет cardinality для этого, он очень легкий и пытается использовать самую быструю из возможных реализаций, доступную в зависимости от повторяемости.

Использование:

>>> import cardinality
>>> cardinality.count([1, 2, 3])
3
>>> cardinality.count(i for i in range(500))
500
>>> def gen():
...     yield 'hello'
...     yield 'world'
>>> cardinality.count(gen())
2
0 голосов
/ 05 июня 2019

Это будет мой выбор, либо тот, либо другой:

print(len([*gen]))
print(len(list(gen)))
...