Czy istnieje skuteczny sposób, aby dowiedzieć się, ile elementów jest w iteratorze w Pythonie, ogólnie, bez iterowania przez każdy z nich i liczenia?
Czy istnieje skuteczny sposób, aby dowiedzieć się, ile elementów jest w iteratorze w Pythonie, ogólnie, bez iterowania przez każdy z nich i liczenia?
Odpowiedzi:
Nie. To niemożliwe.
Przykład:
import random
def gen(n):
for i in xrange(n):
if random.randint(0, 1) == 0:
yield i
iterator = gen(10)
Długość iterator
jest nieznana, dopóki nie przejdziesz przez nią.
def gen(): yield random.randint(0, 1)
jest nieskończona, więc nigdy nie będziesz w stanie znaleźć długości, iterując po niej.
numIters = 0 ; while iterator: numIters +=1
?
Ten kod powinien działać:
>>> iter = (i for i in range(50))
>>> sum(1 for _ in iter)
50
Mimo że wykonuje iterację i zlicza każdy element, jest to najszybszy sposób.
Działa również wtedy, gdy iterator nie ma elementu:
>>> sum(1 for _ in range(0))
0
Oczywiście dla nieskończonych danych wejściowych działa w nieskończoność, więc pamiętaj, że iteratory mogą być nieskończone:
>>> sum(1 for _ in itertools.count())
[nothing happens, forever]
Należy również pamiętać, że iterator zostanie w ten sposób wyczerpany , a dalsze próby jego użycia nie będą zawierać żadnych elementów . To nieunikniona konsekwencja projektu iteratora Pythona. Jeśli chcesz zachować elementy, będziesz musiał przechowywać je na liście lub w czymś takim.
_
odniesienie do Perla $_
? :)
_
dla fikcyjnej zmiennej, której wartość nie jest dla nas ważna .
Nie, każda metoda będzie wymagać rozwiązania każdego wyniku. Możesz to zrobić
iter_length = len(list(iterable))
ale uruchomienie tego na nieskończonym iteratorze oczywiście nigdy nie powróci. Będzie również zużywać iterator i będzie musiał zostać zresetowany, jeśli chcesz użyć zawartości.
Poinformowanie nas, jaki prawdziwy problem próbujesz rozwiązać, może pomóc nam znaleźć lepszy sposób na osiągnięcie rzeczywistego celu.
Edycja: użycie list()
spowoduje natychmiastowe odczytanie całej iteracji do pamięci, co może być niepożądane. Innym sposobem jest zrobienie
sum(1 for _ in iterable)
jako inna osoba. Pozwoli to uniknąć utrzymywania go w pamięci.
len(list(iterable))
go spowoduje załadowanie wszystkich danych do pamięci. Można użyć: reduce(lambda x, _: x+1, iterable, 0)
. Edycja: kod Zonda333 z sumą jest również dobry.
functools.reduce
Nie możesz (poza tym, że typ określonego iteratora implementuje określone metody, które to umożliwiają).
Ogólnie rzecz biorąc, możesz liczyć elementy iteratora tylko przez wykorzystanie iteratora. Jeden z prawdopodobnie najbardziej wydajnych sposobów:
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)
(Pythona 3.x wymienić itertools.izip
z zip
).
sum(1 for _ in iterator)
był prawie dwukrotnie szybszy.
zip
sprawach : jeśli zdasz zip(counter, iterable)
, w rzeczywistości otrzymasz 1 więcej niż liczba iterowalna!
Kinda. Państwo mogli sprawdzić __length_hint__
metodę, ale ostrzegam, że (przynajmniej do Python 3.4, jak gsnedders usłużnie zaznacza) jest to nieudokumentowane szczegółów wdrażania ( po wiadomości w wątku ), które mogłyby równie dobrze zniknąć lub wezwać demony zamiast nosa.
W przeciwnym razie nie. Iteratory to po prostu obiekt, który ujawnia tylko next()
metodę. Możesz sprawdzać to tyle razy, ile potrzeba, a ostatecznie mogą, ale nie muszą, podbić StopIteration
. Na szczęście to zachowanie jest przez większość czasu niewidoczne dla programisty. :)
__length_hint__
jest teraz udokumentowana, ale jest to wskazówka i nie gwarantuje dokładności.
Podoba mi się moc pakiet , jest bardzo lekki i stara się używać najszybszej możliwej implementacji dostępnej w zależności od iterowalnego.
Stosowanie:
>>> 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
Rzeczywista count()
realizacja wygląda następująco:
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
A więc dla tych, którzy chcieliby poznać podsumowanie tej dyskusji. Ostateczne najwyższe wyniki za zliczanie wyrażenia generatora o długości 50 milionów przy użyciu:
len(list(gen))
, len([_ for _ in gen])
, sum(1 for _ in gen),
ilen(gen)
(z more_itertool ),reduce(lambda c, i: c + 1, gen, 0)
, posortowane według wydajności wykonania (w tym zużycia pamięci), sprawi, że będziesz zaskoczony:
`` ''
gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
(„list, sec”, 1,9684218849870376)
gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])
(„list_compr, sec”, 2,5885991149989422)
gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()
(„suma, s”, 3,441088170016883)
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)
gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0)
('zmniejsz, s', 13.436614598002052) ``
Tak więc len(list(gen))
jest to najczęściej i mniej zużywająca się pamięć
len(list(gen))
należy zużywać mniej pamięci niż podejście oparte na redukcji? Pierwsza tworzy nową, list
która obejmuje alokację pamięci, podczas gdy druga nie powinna. Spodziewałbym się więc, że ten ostatni będzie bardziej wydajny w pamięci. Zużycie pamięci zależy również od typu elementu.
len(tuple(iterable))
może być jeszcze wydajniejsze: artykuł Nelsona
Iterator to po prostu obiekt, który ma wskaźnik do następnego obiektu, który ma być odczytany przez jakiś bufor lub strumień, jest jak lista LinkedList, w której nie wiesz, ile masz rzeczy, dopóki nie przejdziesz przez nie. Iteratory mają być wydajne, ponieważ jedyne, co robią, to informowanie cię o tym, co będzie dalej, zamiast korzystania z indeksowania (ale jak zobaczyłeś, tracisz możliwość sprawdzenia, ile wpisów jest następnych).
Jeśli chodzi o twoje pierwotne pytanie, nadal odpowiedź brzmi, że ogólnie nie ma sposobu, aby poznać długość iteratora w Pythonie.
Biorąc pod uwagę, że Twoje pytanie jest motywowane aplikacją biblioteki pysam, mogę udzielić bardziej szczegółowej odpowiedzi: jestem współtwórcą PySAM i ostateczna odpowiedź jest taka, że pliki SAM / BAM nie zapewniają dokładnej liczby wyrównanych odczytów. Informacje te nie są również łatwo dostępne w pliku indeksu BAM. Najlepsze, co można zrobić, to oszacować przybliżoną liczbę wyrównań, używając położenia wskaźnika pliku po odczytaniu liczby dopasowań i ekstrapolacji na podstawie całkowitego rozmiaru pliku. To wystarczy, aby zaimplementować pasek postępu, ale nie metodę zliczania wyrównań w stałym czasie.
Szybki test porównawczy:
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))
Wyniki:
10000 loops, best of 3: 37.2 µs per loop
10000 loops, best of 3: 47.6 µs per loop
10000 loops, best of 3: 61 µs per loop
Tzn. Proste count_iter_items jest drogą do zrobienia.
Dostosowywanie tego dla python3:
61.9 µs ± 275 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
74.4 µs ± 190 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
82.6 µs ± 164 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Istnieją dwa sposoby uzyskania długości „czegoś” na komputerze.
Pierwszym sposobem jest przechowywanie liczby - wymaga to wszystkiego, co dotyka pliku / danych, aby go zmodyfikować (lub klasy, która ujawnia tylko interfejsy - ale sprowadza się do tego samego).
Innym sposobem jest powtórzenie tego i policzenie, jak duże jest.
Jest to sprzeczne z samą definicją iteratora, który jest wskaźnikiem do obiektu oraz informacją o tym, jak dostać się do następnego obiektu.
Iterator nie wie, ile razy będzie w stanie wykonać iterację aż do zakończenia. To może być nieskończone, więc nieskończoność może być twoją odpowiedzią.
Chociaż generalnie nie jest możliwe zrobienie tego, o co zostało poproszone, nadal często warto policzyć, ile elementów zostało powtórzonych po wykonaniu iteracji. W tym celu możesz użyć jaraco.itertools.Counter lub podobnego. Oto przykład użycia Pythona 3 i rwt do załadowania pakietu.
$ rwt -q jaraco.itertools -- -q
>>> import jaraco.itertools
>>> items = jaraco.itertools.Counter(range(100))
>>> _ = list(counted)
>>> items.count
100
>>> import random
>>> def gen(n):
... for i in range(n):
... if random.randint(0, 1) == 0:
... yield i
...
>>> items = jaraco.itertools.Counter(gen(100))
>>> _ = list(counted)
>>> items.count
48
Prawdopodobnie chcesz policzyć liczbę elementów bez iteracji, aby iterator nie został wyczerpany, i użyjesz go ponownie później. Jest to możliwe dzięki copy
lubdeepcopy
import copy
def get_iter_len(iterator):
return sum(1 for _ in copy.copy(iterator))
###############################################
iterator = range(0, 10)
print(get_iter_len(iterator))
if len(tuple(iterator)) > 1:
print("Finding the length did not exhaust the iterator!")
else:
print("oh no! it's all gone")
Wynik to „Finding the length did not exhaust the iterator!
”
Opcjonalnie (i niezalecane) możesz zasłonić wbudowaną len
funkcję w następujący sposób:
import copy
def len(obj, *, len=len):
try:
if hasattr(obj, "__len__"):
r = len(obj)
elif hasattr(obj, "__next__"):
r = sum(1 for _ in copy.copy(obj))
else:
r = len(obj)
finally:
pass
return r
map
iterator oczekujący, że wynikowe wywołania funkcji wystąpią tylko raz.