Długość wyjścia generatora [duplikat]

To pytanie ma już odpowiedź tutaj:

Python zapewnia miłą metodę uzyskiwania długości chętnego iterowalnego, len(x) czyli. Ale nie mogłem znaleźć niczego podobnego dla leniwych iterabli reprezentowanych przez składanie generatorów i funkcje. Oczywiście nie jest trudno napisz coś w stylu:

def iterlen(x):
  n = 0
  try:
    while True:
      next(x)
      n += 1
  except StopIteration: pass
  return n
Ale nie mogę pozbyć się uczucia, że zmieniam rower.

(kiedy pisałem funkcję, uderzyła mnie myśl: może naprawdę nie ma takiej funkcji, ponieważ "niszczy" jej argument. Nie jest to jednak problem dla mojej sprawy).

P. S.: jeśli chodzi o pierwsze odpowiedzi - tak, coś w rodzaju len(list(x)) też by zadziałało, ale to drastycznie zwiększa wykorzystanie pamięci.

P. P. S.: ponownie sprawdzony... Lekceważyć P. S., wydaje mi się, że zrobiłem błąd podczas próby, to działa dobrze. Przepraszam za kłopot.
Author: Smi, 2008-12-25

9 answers

Nie ma takiego, ponieważ nie możesz tego zrobić w ogólnym przypadku - co jeśli masz leniwy nieskończony generator? Na przykład:

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

To nigdy nie kończy się, ale wygeneruje liczby Fibonacciego. Możesz uzyskać dowolną liczbę Fibonacciego, dzwoniąc next().

Jeśli naprawdę potrzebujesz znać liczbę pozycji, nie możesz iterować ich liniowo jeden raz, więc po prostu użyj innej struktury danych, takiej jak zwykła lista.

 32
Author: Adam Rosenfield,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-05 17:03:39

Najprostszym sposobem jest prawdopodobnie sum(1 for _ in gen) gdzie gen jest Twoim generatorem.

 208
Author: Matt Dunham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-13 10:16:04
def count(iter):
    return sum(1 for _ in iter)

Albo jeszcze lepiej:

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

Jeśli nie jest iteracyjna, rzuci TypeError.

Lub, jeśli chcesz policzyć coś konkretnego w generatorze:

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)
 15
Author: mpen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-14 03:48:49

Możesz użyć enumerate (), aby zapętlić wygenerowany strumień danych, a następnie zwrócić ostatnią liczbę -- liczbę pozycji.

Próbowałem użyć itertools.count () z itertools.izip () ale bez powodzenia. To jest najlepsza / Najkrótsza odpowiedź jaką wymyśliłem:
#!/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

Rozwiązanie Kamila Kisiela jest o wiele lepsze:

def count_iterable(i):
    return sum(1 for e in i)
 7
Author: glglgl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-12 15:03:23

Użyj reduce (function, iterable[, initializer]) dla wydajnego pamięci rozwiązania czysto funkcjonalnego:

>>> iter = "This string has 30 characters."
>>> reduce(lambda acc, e: acc + 1, iter, 0)
30
 4
Author: OlivierBlanvillain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-05 08:11:36

Z definicji, tylko podzbiór generatorów powróci po określonej liczbie argumentów (ma predefiniowaną Długość), a nawet wtedy, tylko podzbiór tych skończonych generatorów ma przewidywalny koniec (dostęp do generatora może mieć skutki uboczne, które mogłyby zatrzymać generator wcześniej).

Jeśli chcesz zaimplementować metody length dla swojego generatora, musisz najpierw zdefiniować, co uważasz za "Długość" (czy jest to całkowita liczba elementów? ilość pozostałych elementów?), następnie zapakuj swój generator w klasę. Oto przykład:

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

Oto jak go używać:

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]
 2
Author: sleblanc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-28 19:51:29

Spróbuj more_itertools pakiet dla prostego rozwiązania. Przykład:

>>> import more_itertools

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

>>> more_itertools.ilen(it)
5

Zobacz ten post dla innego przykładu.

 2
Author: pylang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:41

To jest hack, ale jeśli naprawdę chcesz mieć len pracę nad ogólnym iterable( zużywając go w sposób), możesz utworzyć własną wersję len.

Funkcja len jest zasadniczo równoważna następującym (choć implementacje zazwyczaj zapewniają pewne optymalizacje, aby uniknąć dodatkowego wyszukiwania):

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

Dlatego możemy zdefiniować nasze new_len, aby spróbować tego, a jeśli __len__ nie istnieje, policz liczbę elementów samodzielnie, zużywając iterable:

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

The powyższe działa w Pythonie 2/3 i (o ile wiem) powinny obejmować każdy możliwy rodzaj iterable.

 1
Author: yoniLavi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-11 23:35:35

Więc dla tych, którzy chcieliby poznać podsumowanie tej dyskusji. Ostateczne wyniki za zliczanie wyrażenia generatora o długości 50 milionów za pomocą:

  • len(list(gen)),
  • len([_ for _ in gen]),
  • sum(1 for _ in gen),
  • ilen(gen) (from more_itertool),
  • reduce(lambda c, i: c + 1, gen, 0),

Posortowane według wydajności wykonania (w tym zużycia pamięci), zaskoczy Cię:

```

1: test_list.py:8: 0.492 KiB

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

('list, sec', 1.9684218849870376)

2: test_list_compr. py: 8: 0.867 KiB

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 KiB

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

('sum, sec', 3.441088170016883)

4: more_itertools / more. py: 413: 1.266 KiB

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 KiB

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

('reduce, sec', 13.436614598002052) ```

Więc, len(list(gen)) jest najczęstsze i mniej zużywalne pamięci

 1
Author: Alex-Bogdanov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-19 09:29:24