Чи є ефективний спосіб дізнатися, скільки елементів є в ітераторі в 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))
('список, сек', 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))
слід споживати менше пам'яті, ніж підхід, заснований на зменшенні? Перший створює новий, 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
ітератор, очікуючи, що результуючі виклики функції відбудуться лише один раз.