Есть ли эффективный способ узнать, сколько элементов в итераторе в Python, в общем, без перебора каждого и подсчета?
Есть ли эффективный способ узнать, сколько элементов в итераторе в Python, в общем, без перебора каждого и подсчета?
Ответы:
Нет, это невозможно.
Пример:
import random
def gen(n):
for i in xrange(n):
if random.randint(0, 1) == 0:
yield i
iterator = gen(10)
Длина iterator
неизвестна, пока вы не выполните итерацию.
def gen(): yield random.randint(0, 1)
оно бесконечно, поэтому вы никогда не сможете найти длину, перебирая ее.
numIters = 0 ; while iterator: numIters +=1
?
Этот код должен работать:
>>> iter = (i for i in range(50))
>>> sum(1 for _ in iter)
50
Хотя он выполняет итерацию по каждому элементу и считает их, это самый быстрый способ сделать это.
Это также работает, когда итератор не имеет элемента:
>>> sum(1 for _ in range(0))
0
Конечно, он работает вечно для бесконечного ввода, поэтому помните, что итераторы могут быть бесконечными:
>>> sum(1 for _ in itertools.count())
[nothing happens, forever]
Также имейте в виду, что при этом итератор будет исчерпан , и дальнейшие попытки его использования не будут видеть элементов . Это неизбежное следствие дизайна итератора Python. Если вы хотите сохранить элементы, вам придется хранить их в списке или что-то в этом роде.
_
ссылка на Perl $_
? :)
_
для фиктивной переменной, значение которой вас не волнует.
Нет, любой метод потребует от вас разрешения каждого результата. Ты можешь сделать
iter_length = len(list(iterable))
но выполнение этого на бесконечном итераторе, конечно, никогда не вернется. Он также будет использовать итератор, и его необходимо будет сбросить, если вы хотите использовать содержимое.
Если вы сообщите нам, какую реальную проблему вы пытаетесь решить, это может помочь вам найти более эффективный способ достижения вашей реальной цели.
Изменить: Использование list()
будет читать все повторяемые в памяти сразу, что может быть нежелательно. Другой способ сделать
sum(1 for _ in iterable)
как написал другой человек. Это позволит избежать сохранения в памяти.
len(list(iterable))
это загрузит все данные в память. Вы можете использовать: reduce(lambda x, _: x+1, iterable, 0)
. Изменить: Zonda333 код с суммой тоже хорошо.
functools.reduce
Вы не можете (кроме типа конкретного итератора реализует некоторые конкретные методы, которые делают это возможным).
Как правило, вы можете считать элементы итератора только, потребляя итератор. Один из, вероятно, самых эффективных способов:
import itertools
from collections import deque
def count_iter_items(iterable):
"""
Consume an iterable not reading it into memory; return the number of items.
"""
counter = itertools.count()
deque(itertools.izip(iterable, counter), maxlen=0) # (consume at C speed)
return next(counter)
(Для Python 3.x заменить itertools.izip
на zip
).
sum(1 for _ in iterator)
это было почти в два раза быстрее.
zip
имеет значение : если вы пройдете zip(counter, iterable)
, вы на самом деле получите на 1 больше, чем количество итераций!
Вроде. Вы можете проверить __length_hint__
метод, но имейте в виду, что (по крайней мере, до Python 3.4, как подсказывает gsnedders), это недокументированная деталь реализации ( после сообщения в теме ), которая может очень легко исчезнуть или вызвать назальных демонов.
В противном случае нет. Итераторы - это просто объект, который раскрывает только next()
метод. Вы можете назвать это столько раз, сколько потребуется, и они могут или не могут в конечном итоге повысить StopIteration
. К счастью, такое поведение в большинстве случаев прозрачно для кодировщика. :)
Мне нравится пакет мощности для этого, он очень легкий и пытается использовать самую быструю из возможных реализаций в зависимости от итерируемого.
Использование:
>>> 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
Фактическая count()
реализация выглядит следующим образом:
def count(iterable):
if hasattr(iterable, '__len__'):
return len(iterable)
d = collections.deque(enumerate(iterable, 1), maxlen=1)
return d[0][0] if d else 0
Итак, для тех, кто хотел бы узнать краткое содержание этого обсуждения. Итоговые максимальные оценки для подсчета выражения генератора длиной 50 миллионов с использованием:
len(list(gen))
, len([_ for _ in gen])
, sum(1 for _ in gen),
ilen(gen)
(из more_itertool ),reduce(lambda c, i: c + 1, gen, 0)
, отсортированный по производительности выполнения (включая потребление памяти), удивит вас:
`` `
gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
('list, sec', 1.9684218849870376)
gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])
('list_compr, sec', 2.5885991149989422)
gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()
(«сумма, сек», 3.441088170016883)
d = deque(enumerate(iterable, 1), maxlen=1)
test_ilen.py:10: 0.875 KiB
gen = (i for i in data*1000); t0 = monotonic(); ilen(gen)
('ilen, sec', 9.812256851990242)
gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0)
(«уменьшить, сек», 13.436614598002052) `` `
Итак, len(list(gen))
это самый частый и менее потребляемый объем памяти
len(list(gen))
следует использовать меньше памяти, чем подход, основанный на методе Reduce? Первый создает новый, list
который включает в себя распределение памяти, в то время как последний не должен. Так что я ожидаю, что последний будет более эффективным с точки зрения памяти. Кроме того, потребление памяти будет зависеть от типа элемента.
len(tuple(iterable))
может быть еще более эффективным: статья Нельсона Минара
Итератор - это просто объект, у которого есть указатель на следующий объект, который должен быть прочитан каким-либо буфером или потоком, он похож на LinkedList, где вы не знаете, сколько у вас есть вещей, пока не выполните их итерацию. Предполагается, что итераторы эффективны, потому что все, что они делают, - это сообщают вам, что дальше, по ссылкам, а не используют индексацию (но, как вы видели, вы теряете способность видеть, сколько записей дальше).
Что касается вашего первоначального вопроса, ответ по-прежнему заключается в том, что в общем случае нет способа узнать длину итератора в Python.
Учитывая, что ваш вопрос мотивирован приложением библиотеки pysam, я могу дать более конкретный ответ: я участвую в PySAM, и однозначный ответ заключается в том, что файлы SAM / BAM не обеспечивают точного количества выровненных чтений. Также эта информация не легко доступна из индексного файла BAM. Лучшее, что можно сделать, - это оценить приблизительное количество выравниваний, используя расположение указателя файла после считывания ряда выравниваний и экстраполяции на основе общего размера файла. Этого достаточно, чтобы реализовать индикатор выполнения, но не метод подсчета выравниваний за постоянное время.
Быстрый тест:
import collections
import itertools
def count_iter_items(iterable):
counter = itertools.count()
collections.deque(itertools.izip(iterable, counter), maxlen=0)
return next(counter)
def count_lencheck(iterable):
if hasattr(iterable, '__len__'):
return len(iterable)
d = collections.deque(enumerate(iterable, 1), maxlen=1)
return d[0][0] if d else 0
def count_sum(iterable):
return sum(1 for _ in iterable)
iter = lambda y: (x for x in xrange(y))
%timeit count_iter_items(iter(1000))
%timeit count_lencheck(iter(1000))
%timeit count_sum(iter(1000))
Результаты:
10000 loops, best of 3: 37.2 µs per loop
10000 loops, best of 3: 47.6 µs per loop
10000 loops, best of 3: 61 µs per loop
Т.е. простой count_iter_items - это путь.
Настраиваем это для python3:
61.9 µs ± 275 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
74.4 µs ± 190 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
82.6 µs ± 164 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Есть два способа получить длину «чего-то» на компьютере.
Первый способ - сохранить счетчик - для его изменения требуется все, что касается файла / данных (или класс, который предоставляет только интерфейсы, но сводится к одному и тому же).
Другой способ - перебрать его и посчитать, насколько он велик.
Это противоречит самому определению итератора, который является указателем на объект, плюс информация о том, как добраться до следующего объекта.
Итератор не знает, сколько еще раз он сможет выполнить итерацию до завершения. Это может быть бесконечно, поэтому бесконечность может быть вашим ответом.
Хотя в общем и целом невозможно выполнить то, что было задано, все равно часто полезно подсчитывать, сколько элементов было повторено после их повторения. Для этого вы можете использовать jaraco.itertools.Counter или аналогичный. Вот пример использования Python 3 и rwt для загрузки пакета.
$ rwt -q jaraco.itertools -- -q
>>> import jaraco.itertools
>>> items = jaraco.itertools.Counter(range(100))
>>> _ = list(counted)
>>> items.count
100
>>> import random
>>> def gen(n):
... for i in range(n):
... if random.randint(0, 1) == 0:
... yield i
...
>>> items = jaraco.itertools.Counter(gen(100))
>>> _ = list(counted)
>>> items.count
48
Предположительно, вы хотите посчитать количество элементов без итераций, чтобы итератор не был исчерпан, и вы будете использовать его позже. Это возможно с copy
илиdeepcopy
import copy
def get_iter_len(iterator):
return sum(1 for _ in copy.copy(iterator))
###############################################
iterator = range(0, 10)
print(get_iter_len(iterator))
if len(tuple(iterator)) > 1:
print("Finding the length did not exhaust the iterator!")
else:
print("oh no! it's all gone")
Выход " Finding the length did not exhaust the iterator!
"
По желанию (и неосознанно) вы можете скрыть встроенную len
функцию следующим образом:
import copy
def len(obj, *, len=len):
try:
if hasattr(obj, "__len__"):
r = len(obj)
elif hasattr(obj, "__next__"):
r = sum(1 for _ in copy.copy(obj))
else:
r = len(obj)
finally:
pass
return r
map
итератор, ожидающий, что вызовы функций будут происходить только один раз.