Получение количества элементов в итераторе в Python - PullRequest
107 голосов
/ 27 июля 2010

Есть ли эффективный способ узнать, сколько элементов в итераторе в Python, в общем, без перебора каждого и подсчета?

Ответы [ 15 ]

184 голосов
/ 27 июля 2010

Этот код должен работать:

>>> iter = (i for i in range(50))
>>> sum(1 for _ in iter)
50

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

>>> sum(1 for _ in range(0))
0
88 голосов
/ 27 июля 2010

Нет. Это невозможно.

Пример:

import random

def gen(n):
    for i in xrange(n):
        if random.randint(0, 1) == 0:
            yield i

iterator = gen(10)

Длина iterator неизвестна, пока вы не выполните итерацию.

61 голосов
/ 27 июля 2010

Нет, любой метод потребует от вас разрешения каждого результата.Вы можете сделать

iter_length = len(list(iterable))

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

Если вы сообщите нам, какую реальную проблему вы пытаетесь решить, это может помочь вам найти лучший способ выполнить вашу реальную задачу.цель.

Редактировать: Использование list() сразу прочитает всю итерацию в память, что может быть нежелательно.Другой способ - сделать

sum(1 for _ in iterable)

как другой человек.Это позволит избежать сохранения в памяти.

26 голосов
/ 27 февраля 2013

Вы не можете (за исключением того, что тип конкретного итератора реализует некоторые конкретные методы, которые делают это возможным).

Как правило, вы можете считать элементы итератора только, потребляя итератор.Один из, вероятно, наиболее эффективных способов:

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).

16 голосов
/ 27 июля 2010

Любопытное.Вы можете проверить метод __length_hint__, но имейте в виду, что (по крайней мере, до Python 3.4, как подсказывает gsnedders), это недокументированная деталь реализации ( после сообщенияв потоке ), это может очень хорошо исчезнуть или вызвать назальных демонов.

В противном случае нет.Итераторы - это просто объект, который предоставляет только метод next().Вы можете вызывать его столько раз, сколько потребуется, и они могут или не могут в конечном итоге повысить StopIteration.К счастью, такое поведение в большинстве случаев прозрачно для кодировщика.:)

9 голосов
/ 15 апреля 2016

Мне нравится пакет cardinality для этого, он очень легкий и пытается использовать самую быструю из возможных реализаций в зависимости от итерируемого.1007 * Фактическая реализация 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
9 голосов
/ 27 июля 2010

Итератор - это просто объект, у которого есть указатель на следующий объект, который должен быть прочитан каким-либо буфером или потоком, он похож на LinkedList, где вы не знаете, сколько у вас есть вещей, пока не выполните итерацию по ним. Предполагается, что итераторы эффективны, потому что все, что они делают, - это сообщают вам, что дальше, по ссылкам, а не используют индексацию (но, как вы видели, вы теряете способность видеть, сколько записей дальше).

7 голосов
/ 17 августа 2010

Что касается вашего первоначального вопроса, ответ по-прежнему заключается в том, что в общем случае нет способа узнать длину итератора в Python.

Учитывая, что ваш вопрос мотивирован приложением библиотеки pysam, я могу дать более конкретный ответ: я участвую в PySAM, и однозначный ответ заключается в том, что файлы SAM / BAM не предоставляют точное количество выровненных читает. Также эта информация не легко доступна из индексного файла BAM. Лучшее, что можно сделать, - это оценить приблизительное количество выравниваний, используя местоположение указателя файла после считывания ряда выравниваний и экстраполяции на основе общего размера файла. Этого достаточно, чтобы реализовать индикатор выполнения, но не метод подсчета выравниваний за постоянное время.

4 голосов
/ 12 июня 2017

Быстрый тест:

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: 35.4 µs per loop
10000 loops, best of 3: 40.2 µs per loop
10000 loops, best of 3: 50.7 µs per loop

Т.е. простой count_iter_items - это путь.

3 голосов
/ 19 июня 2018

Итак, для тех, кто хотел бы узнать краткое содержание этого обсуждения. Окончательные максимальные оценки для подсчета выражения генератора длиной 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),

, отсортированный по производительности выполнения (включая потребление памяти), удивит вас:

`` `

1: test_list.py:8: 0,492 КиБ

gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))

('list, sec', 1.9684218849870376)

2: test_list_compr.py:8: 0,867 КиБ

gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])

('list_compr, sec', 2.5885991149989422)

3: test_sum.py:8: 0,859 КиБ

gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()

(«сумма, сек», 3.441088170016883)

4: more_itertools / more.py: 413: 1,266 КиБ

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)

5: test_reduce.py:8: 0,859 КиБ

gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0)

(«уменьшить, сек», 13.436614598002052) `` `

Итак, len(list(gen)) является наиболее частым и менее потребляемым объемом памяти

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...