Вопрос

Python предоставляет хороший метод для получения длины нетерпеливой итерации: len(x) то есть.Но я не смог найти ничего подобного для ленивых итераций, представленных генераторами и функциями.Конечно, нетрудно написать что-то вроде:

def iterlen(x):
  n = 0
  try:
    while True:
      next(x)
      n += 1
  except StopIteration: pass
  return n

Но не могу избавиться от ощущения, что переделываю велосипед.

(Пока я печатал функцию, мне в голову пришла мысль:возможно, такой функции действительно нет, потому что она «уничтожает» свой аргумент.Хотя для моего случая это не проблема).

P.S.:по поводу первых ответов - да, что-то вроде len(list(x)) тоже будет работать, но это резко увеличивает использование памяти.

П.П.С.:перепроверил...P.S. Не обращайте внимания, кажется, я допустил ошибку при попытке, все работает нормально.Извините за беспокойство.

Это было полезно?

Решение

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

def fib():
    a, b = 0, 1
    while True:
        a, b = b, a + b
        yield a

Это никогда не заканчивается, но будет генерировать числа Фибоначчи.Вы можете получить столько чисел Фибоначчи, сколько захотите, позвонив next().

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

Другие советы

Самый простой способ, вероятно, просто sum(1 for _ in gen) где gen — ваш генератор.

def count(iter):
    return sum(1 for _ in iter)

Или еще лучше:

def count(iter):
    try:
        return len(iter)
    except TypeError:
        return sum(1 for _ in iter)

Если он не повторяется, он выдаст TypeError.

Или, если вы хотите посчитать в генераторе что-то конкретное:

def count(iter, key=None):
    if key:
        if callable(key):
            return sum(bool(key(x)) for x in iter)
        return sum(x == key for x in iter)
    try:
        return len(iter)
    except TypeError:
        return sum(1 for _ in iter)

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

('список, сек', 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, сек', 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)

('илен, сек', 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)

('Cream, Sec', 13,436614598002052) `` `` `` `` `` ``

Так, len(list(gen)) является наиболее частым и менее потребляющим память

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

Я пытался использовать itertools.count() с itertools.izip(), но безуспешно.Это лучший/самый короткий ответ, который я придумал:

#!/usr/bin/python

import itertools

def func():
    for i in 'yummy beer':
        yield i

def icount(ifunc):
    size = -1 # for the case of an empty iterator
    for size, _ in enumerate(ifunc()):
        pass
    return size + 1

print list(func())
print 'icount', icount(func)

# ['y', 'u', 'm', 'm', 'y', ' ', 'b', 'e', 'e', 'r']
# icount 10

Решение Камиля Кисиэля намного лучше:

def count_iterable(i):
    return sum(1 for e in i)

Использовать уменьшить (функция, итерируемый [, инициализатор]) для эффективного использования памяти чисто функционального решения:

>>> iter = "This string has 30 characters."
>>> reduce(lambda acc, e: acc + 1, iter, 0)
30

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

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

class MyFib(object):
    """
    A class iterator that iterates through values of the
    Fibonacci sequence, until, optionally, a maximum length is reached.
    """

    def __init__(self, length):
        self._length = length
        self._i = 0

     def __iter__(self):
        a, b = 0, 1
        while not self._length or self._i < self._length:
            a, b = b, a + b
            self._i += 1
            yield a

    def __len__(self):
        "This method returns the total number of elements"
        if self._length:
            return self._length
        else:
            raise NotImplementedError("Infinite sequence has no length")
            # or simply return None / 0 depending
            # on implementation

Вот как его использовать:

In [151]: mf = MyFib(20)

In [152]: len(mf)
Out[152]: 20

In [153]: l = [n for n in mf]

In [154]: len(l)
Out[154]: 20

In [155]: l
Out[155]: 
[1,
 1,
 2,
...
6765]


In [156]: mf0 = MyFib(0)

In [157]: len(mf0)
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-157-2e89b32ad3e4> in <module>()
----> 1 len(mf0)

/tmp/ipython_edit_TWcV1I.py in __len__(self)
     22             return self._length
     23         else:
---> 24             raise NotImplementedError
     25             # or simply return None / 0 depending
     26             # on implementation

NotImplementedError: 

In [158]: g = iter(mf0)

In [159]: l0 = [g.next(), g.next(), g.next()]

In [160]: l0
Out[160]: [1, 1, 2]

Попробуйте more_itertools пакет для простого решения.Пример:

>>> import more_itertools

>>> it = iter("abcde")                                         # sample generator
>>> it
<str_iterator at 0x4ab3630>

>>> more_itertools.ilen(it)
5

Видеть эта почта еще один прикладной пример.

Это хак, но если вы действительно хотите иметь len работая над общей итерацией (попутно потребляя ее), вы можете создать свою собственную версию len.

А len функция по существу эквивалентна следующей (хотя реализации обычно предусматривают некоторую оптимизацию, чтобы избежать дополнительного поиска):

def len(iterable):
    return iterable.__len__()

Поэтому мы можем определить нашу new_len попробовать это, и если __len__ не существует, посчитайте количество элементов самостоятельно, потребляя итерацию:

def new_len(iterable):
    try:
      return iterable.__len__()
    except AttributeError:
      return sum(1 for _ in iterable)

Вышеупомянутое работает в Python 2/3 и (насколько мне известно) должно охватывать все мыслимые виды итераций.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top