2011-03-21 2 views
48

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

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

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

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

+4

Пожалуйста, не используйте '_' в качестве имени переменной, потому что (1) она стремится запутать людей, заставляя их думать, это какой-то специальный синтаксис, (2) сталкивается с' _' в интерактивный интерпретатор и (3) сталкивается с общим псевдонимом gettext. –

+4

@Sven: Я использую '_' все время для неиспользуемых переменных (привычка от программирования Prolog и Haskell). (1) является основанием для того, чтобы просить об этом в первую очередь. Я не рассматривал (2) и (3), спасибо за указание их! –

+2

duplicated: http://stackoverflow.com/questions/390852/is-there-any-built-in-way-to-get-the-length-of-an-iterable-in-python – tokland

ответ

92

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

sum(1 for i in it) 
+1

вы можете использовать 'len (list (it)) '- или если элементы уникальны, тогда' len (set (it)) 'для сохранения символа. – F1Rumors

+6

@ F1Rumors Использование 'len (list (it))' отлично в большинстве случаев. Однако, когда у вас есть ленивый итератор, уступающий множеству и множеству элементов, вы не хотите хранить их все в памяти одновременно, просто чтобы подсчитать их, чего избежать можно с помощью кода в этом ответе. –

+0

согласился: в качестве ответа он был основан на «кратчайшем» коде, который более важен, чем «самая низкая память». – F1Rumors

5

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

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

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

+0

Я думал об этом, но производительность имеет значение, поскольку я часто обрабатываю большие текстовые файлы. –

+6

До тех пор, пока у вас не закончится память, это решение на самом деле неплохое по производительности, так как это сделает цикл в чистом C-коде - все объекты должны быть созданы в любом случае. Даже для больших итераторов это быстрее, чем 'sum (1 для i в нем)' до тех пор, пока все вписывается в память. –

14

метод, который значительно быстрее, чем 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 (dequecount, и 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 магия (подставляя разные константы 100):

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

Если вход не настолько велик, что len(list(it)) может вызвать проблемы с памятью, на коробке Linux работает Python 3.5 x64, мое решение занимает около 50% больше тха n 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).

1

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

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

import more_itertools as mit 


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

 Смежные вопросы

  • Нет связанных вопросов^_^