Подтвердить что ты не робот

Какой самый короткий способ подсчета количества элементов в генераторе/итераторе?

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

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

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

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

4b9b3361

Ответ 1

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

sum(1 for i in it)

Ответ 2

Метод, который значительно быстрее, чем sum(1 for i in it), когда итерабельность может быть длинной (и не значимо медленнее, если iterable является коротким), сохраняя при этом поведение накладных расходов фиксированной памяти (в отличие от 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).

Ответ 3

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

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

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

Ответ 4

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

import more_itertools as mit


mit.ilen(x for x in range(10))
# 10

Ответ 5

Мне нравится 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

Ответ 6

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

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