2011-03-21 17 views
48

Jeśli chcę mieć liczbę elementów w grupie iterowalnej, nie troszcząc się o same elementy, jaki byłby to python? Teraz, chciałbym zdefiniowaćJaki jest najkrótszy sposób zliczania liczby elementów w generatorze/iteratorze?

def ilen(it): 
    return sum(itertools.imap(lambda _: 1, it)) # or just map in Python 3 

ale rozumiem lambda jest blisko są uważane za szkodliwe i lambda _: 1 pewnością nie jest ładna.

(Sprawa wykorzystanie tego liczy liczbę wierszy w pliku tekstowym dopasowanie regex, tj grep -c.)

+4

Proszę nie używać '_' jako nazwy zmiennej, ponieważ (1) ma tendencję do mylić ludzi, czyniąc z nich, że to jest jakiś rodzaj specjalnej składni, (2) zderza się z' _' w Interaktywny interpreter i (3) koliduje ze wspólnym aliasem Gettext. –

+4

@Sven: Cały czas używam '_' dla nieużywanych zmiennych (nawyk z programowania Prolog i Haskell). (1) jest powodem, dla którego należy to przede wszystkim zadać. Nie uważałem (2) i (3), dziękuję za wskazanie ich! –

+2

powielone: ​​http://stackoverflow.com/questions/390852/is-there-any-built-in-way-to-get-tength-of-anable-in-python – tokland

Odpowiedz

92

Zwykłym sposobem jest

sum(1 for i in it) 
+1

możesz użyć 'len (list (it)) '- lub jeśli elementy są unikalne, następnie' len (set (it)), aby zapisać znak. – F1Rumors

+6

@ F1Rumors Użycie 'len (list (it))' jest w większości przypadków w porządku. Jednak, gdy masz leniwy iterator, który dostarcza wiele elementów, nie chcesz przechowywać ich wszystkich w pamięci jednocześnie, aby je policzyć, czego unika się przy użyciu kodu w tej odpowiedzi. –

+0

zgodził się: jako odpowiedź, było oparte na "najkrótszym kodzie", ważniejszym niż "najniższa pamięć". – F1Rumors

5

Krótka droga jest:

def ilen(it): 
    return len(list(it)) 

Uwaga: jeśli generujesz część elementów (powiedzmy, dziesiątki tysięcy lub więcej), a następnie umieszczaj je na liście może stać się problemem z wydajnością. Jest to jednak prosty wyraz idei, w której wydajność w większości przypadków nie ma znaczenia.

+0

Pomyślałem o tym, ale wydajność ma znaczenie, ponieważ często przetwarzam duże pliki tekstowe. –

+6

Tak długo, jak nie zabraknie pamięci, to rozwiązanie jest całkiem niezłe, ponieważ spowoduje to utworzenie pętli w czystym kodzie C - mimo to wszystkie obiekty muszą zostać wygenerowane. Nawet dla dużych iteratorów jest to szybsze niż 'suma (1 dla i w nim)' o ile wszystko pasuje do pamięci. –

14

metoda, która jest znacząco szybsza niż sum(1 for i in it) gdy iterable mogą być długie (i nie znacząco wolniej gdy iterable jest krótki), przy zachowaniu stałej pamięci napowietrznych zachowanie (w przeciwieństwie len(list(it))), aby uniknąć wymiany lanie i realokacji narzut dla większych nakładów:

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

jak len(list(it)) wykonuje pętlę w kodzie C na CPython (wszystkie deque, count i zip są realizowane w c); unikanie wykonywania kodu bajtowego na pętlę jest zwykle kluczem do wydajności w CPython.

Jest zaskakująco trudno wymyślić uczciwych przypadków testowych dla porównania wydajności (list cheaty wykorzystujące __length_hint__ który nie może być dostępna dla dowolnych iterables wejściowych, itertools funkcje, które nie zapewniają __length_hint__ często mają specjalne tryby pracy, które działają szybciej, gdy zwrócona wartość w każdej pętli zostanie zwolniona, zanim zażąda kolejnej wartości, co zrobi deque z maxlen=0). W przypadku badania że użyto utworzyć funkcję generatora, która weźmie wejście i powrót generator poziomu C pozbawionymi specjalny itertools optymalizacji pojemnik zwrotny lub __length_hint__ używając Pyton 3,3 na yield from:

def no_opt_iter(it): 
    yield from it 

Następnie przy ipython%timeit magia (podstawiając różne stałe na 100):

>>> %%timeit -r5 fakeinput = (0,) * 100 
... ilen(no_opt_iter(fakeinput)) 

Gdy wejście nie jest na tyle duża, że ​​len(list(it)) spowodowałoby problemy z pamięcią, na Linuksie systemem Pythona 3.5 x64, moje rozwiązanie zajmuje około 50% dłużej tha n def ilen(it): return len(list(it)), niezależnie od długości wejścia.

dla najmniejszych nakładów, koszty instalacji zadzwonić deque/zip/count/next Oznacza to trwa nieskończenie dłużej w ten sposób niż def ilen(it): sum(1 for x in it) (około 200 ns więcej na moim komputerze na długość 0 wejściu, co jest 33% wzrost w stosunku do prostego podejścia sum), ale przy dłuższych nakładach trwa około połowę czasu na dodatkowy element; dla długości 5 wejść, koszt jest równoważny, a gdzieś w zakresie długości 50-100, początkowy narzut jest niezauważalny w porównaniu do prawdziwej pracy; podejście sum zajmuje około dwa razy dłużej.

Zasadniczo, jeśli użycie pamięci ma znaczenie lub dane wejściowe nie mają dużego rozmiaru i zależy Ci na szybkości większej niż zwięzłość, skorzystaj z tego rozwiązania. Jeśli dane wejściowe są ograniczone i niewielkie, prawdopodobnie najlepiej jest uzyskać len(list(it)), a jeśli są nieograniczone, ale liczy się prostota/zwięzłość, użyjemy sum(1 for x in it).

1

Podoba mi się pakiet cardinality, jest bardzo lekki i próbuje użyć najszybszej możliwej implementacji dostępnej w zależności od iteracji.

Zastosowanie:

>>> 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 
1

more_itertools jest biblioteką innej firmy, która implementuje narzędzie ilen. pip install more_itertools

import more_itertools as mit 


mit.ilen(x for x in range(10)) 
# 10 
Powiązane problemy