Jaki jest najkrótszy sposób policzenia liczby elementów w generatorze / iteratorze?
Jeśli chcę liczbę przedmiotów w iterable bez dbania o same elementy, jaki byłby pythoniczny sposób, aby to uzyskać? W tej chwili zdefiniowałbym
def ilen(it):
return sum(itertools.imap(lambda _: 1, it)) # or just map in Python 3
Ale rozumiem, że jest blisko bycia uważanym za szkodliwe i z pewnością nie jest ładne.
(ten przypadek użycia polega na zliczaniu liczby wierszy w pliku tekstowym pasujących do wyrażenia regularnego, tj. grep -c
.)
7 answers
Wywołania itertools.imap()
w Pythonie 2 lub map()
w Pythonie 3 mogą być zastąpione równoważnymi wyrażeniami generatora:
sum(1 for dummy in it)
Używa również leniwego generatora, dzięki czemu unika się zmaterializowania pełnej listy wszystkich elementów iteratora w pamięci.
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
2020-06-15 07:26:49
Metoda, która jest znacznie szybsza niż sum(1 for i in it)
, gdy iterable może być długi (a nie znacznie wolniejszy, gdy iterable jest krótki), przy zachowaniu stałego narzutu pamięci (w przeciwieństwie do len(list(it))
), Aby uniknąć wymiany narzutu i realokacji na większe wejścia:
# On Python 2 only, get zip that lazily generates results instead of returning list
from future_builtins import zip
from collections import deque
from itertools import count
def ilen(it):
# Make a stateful counting iterator
cnt = count()
# zip it with the input iterator, then drain until input exhausted at C level
deque(zip(it, cnt), 0) # cnt must be second zip arg to avoid advancing too far
# Since count 0 based, the next value is the count
return next(cnt)
Podobnie jak len(list(it))
wykonuje pętlę w kodzie C na Cpythonie (deque
, count
i zip
są zaimplementowane w C); unikanie wykonania kodu bajtowego na pętlę jest zwykle kluczem do wydajności w CPython.
Zaskakująco trudno jest wymyślić uczciwe przypadki testowe do porównywania wydajności (list
cheaty przy użyciu __length_hint__
, które prawdopodobnie nie będą dostępne dla dowolnych iterabli wejściowych, itertools
funkcje, które nie dostarczają __length_hint__
często mają specjalne tryby pracy, które działają szybciej, gdy wartość zwracana w każdej pętli jest zwolniona / zwolniona przed żądaniem następnej wartości, co deque
z maxlen=0
wystarczy). Przypadek testowy, którego użyłem, polegał na stworzeniu funkcji generatora, która zajęłaby wprowadzenie i zwrócenie generatora poziomu C, który nie miał specjalnych itertools
zwraca optymalizacje kontenera lub __length_hint__
, używając Pythona 3.3 yield from
:
def no_opt_iter(it):
yield from it
Następnie używając ipython
%timeit
Magia (podstawienie różnych stałych dla 100):
>>> %%timeit -r5 fakeinput = (0,) * 100
... ilen(no_opt_iter(fakeinput))
Gdy wejście nie jest wystarczająco duże, aby len(list(it))
powodowało problemy z pamięcią, na Linuksie z Pythonem 3.5 x64 moje rozwiązanie trwa około 50% dłużej niż def ilen(it): return len(list(it))
, niezależnie od długości wejścia.
Dla najmniejszego z wejść, konfiguracja kosztuje call deque
/zip
/count
/next
oznacza to, że w ten sposób zajmuje nieskończenie dużo więcej czasu niż def ilen(it): sum(1 for x in it)
(około 200 ns więcej na mojej maszynie dla wejścia o długości 0, co jest 33% wzrostem w porównaniu z prostym podejściem sum
), ale dla dłuższych wejść, działa w około połowie czasu na dodatkowy element; dla wejść o długości 5, koszt jest równoważny, a gdzieś w zakresie długości 50-100, początkowy narzut jest niezauważalny w porównaniu z rzeczywistą pracą; podejście {27]} zajmuje mniej więcej dwa razy więcej czasu niż w rzeczywistości; długa.
Zasadniczo, jeśli użycie pamięci ma znaczenie lub dane wejściowe nie mają ograniczonego rozmiaru i zależy Ci bardziej na szybkości niż zwięzłości, użyj tego rozwiązania. Jeśli Dane wejściowe są ograniczone i małe, len(list(it))
jest prawdopodobnie najlepsze, a jeśli są nieograniczone, ale liczy się prostota/zwięzłość, użyjesz sum(1 for x in it)
.
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
2020-06-03 15:01:26
Krótka droga to:
def ilen(it):
return len(list(it))
Zauważ, że jeśli generujesz partię elementów (powiedzmy dziesiątki tysięcy lub więcej), umieszczenie ich na liście może stać się problemem z wydajnością. Jest to jednak proste wyrażenie idei, w której wydajność nie będzie miała znaczenia w większości przypadków.
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
2011-03-21 22:39:10
more_itertools
jest biblioteką innej firmy, która implementuje ilen
narzędzie. pip install more_itertools
import more_itertools as mit
mit.ilen(x for x in range(10))
# 10
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-03-07 16:17:40
Podoba mi się pakiet cardinality do tego jest bardzo lekki i stara się wykorzystać jak najszybszą dostępną implementację w zależności od iterable.
Użycie:
>>> 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
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-04-15 10:28:04
To byłby mój wybór albo jeden, albo drugi:
print(len([*gen]))
print(len(list(gen)))
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
2019-06-05 18:28:50
len(list(it))
Chociaż może się rozłączyć, jeśli jest nieskończonym generatorem.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
2021-01-08 12:04:00