Alternatywnie, 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.
tgray
1
Tak więc, aby potwierdzić oczywistość: najlepszym sposobem na uzyskanie „rozmiaru” iteratora jest po prostu policzenie, ile razy przeszedłeś przez iterację, prawda? W takim przypadku byłoby to numIters = 0 ; while iterator: numIters +=1?
Mike Williamson,
Ciekawe, więc jest to problem z zatrzymaniem
Akababa
231
Ten kod powinien działać:
>>> iter =(i for i in range(50))>>> sum(1for _ 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(1for _ 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(1for _ 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.
Wydaje mi się, że robi to dokładnie to, czego OP nie chce robić: iteruje iterator i liczy.
Adam Crossland
36
Jest to efektywny przestrzennie sposób liczenia elementów w iterowalnym
Captain Lepton,
9
Chociaż nie tego chce OP, biorąc pod uwagę, że jego pytanie nie ma odpowiedzi, ta odpowiedź pozwala uniknąć tworzenia instancji listy i jest empirycznie szybsza dzięki stałej niż metoda redukcji wymieniona powyżej.
Phillip Nordwall
5
Nic na to nie poradzę: czy jest to _odniesienie do Perla $_? :)
Alois Mahdal
17
@AloisMahdal Nie. W Pythonie zwyczajowo używa się nazwy _dla fikcyjnej zmiennej, której wartość nie jest dla nas ważna .
Taymon
67
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(1for _ in iterable)
jako inna osoba. Pozwoli to uniknąć utrzymywania go w pamięci.
problem polega na tym, że czytam plik z "pysam", który ma miliony wpisów. Pysam zwraca iterator. Aby obliczyć określoną ilość, muszę wiedzieć, ile odczytów znajduje się w pliku, ale nie muszę czytać każdego z nich ... to jest problem.
6
Nie jestem użytkownikiem pysam, ale prawdopodobnie czyta plik „leniwy”. Ma to sens, ponieważ nie chcesz mieć dużego pliku w pamięci. Więc jeśli musisz wiedzieć, nie. rekordów przed iteracją, jedynym sposobem jest utworzenie dwóch iteratorów i użycie pierwszego do zliczania elementów, a drugiego do odczytu pliku. BTW. Nie używaj 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.
Tomasz Wysocki
1
@ user248237: dlaczego mówisz, że musisz wiedzieć, ile wpisów jest dostępnych, aby obliczyć określoną ilość? Możesz po prostu przeczytać określoną ich liczbę i zarządzać przypadkiem, gdy jest ich mniej niż ustalona ilość (naprawdę proste do zrobienia za pomocą iterslice). Czy jest jeszcze jeden powód, dla którego musisz czytać wszystkie wpisy?
kriss
1
@Tomasz Zwróć uwagę, że funkcja Redukcja jest przestarzała i zniknie w Pythonie 3 i nowszych.
Wilduck
7
@Wilduck: Nie ma go, właśnie przeniósł się dofunctools.reduce
Daenyth
33
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)
+1: w porównaniu z czasem sum(1 for _ in iterator)był prawie dwukrotnie szybszy.
augustomen
1
Dokładniej jest powiedzieć, że zużywa iterowalność, wczytując każdy element z pamięci i od razu go odrzucając.
Rockallite
Należy zauważyć (co przeoczyłem), że kolejność argumentów w zipsprawach : jeśli zdasz zip(counter, iterable), w rzeczywistości otrzymasz 1 więcej niż liczba iterowalna!
Kye W Shi
bardzo ładna odpowiedź. dałoby za to nagrodę.
Reut Sharabani
18
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. :)
Nie ma to już miejsca, od PEP 424 i Python 3.4. __length_hint__jest teraz udokumentowana, ale jest to wskazówka i nie gwarantuje dokładności.
gsnedders,
12
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
Zakładam, że nadal możesz iterować iterator, jeśli używasz tej funkcji, tak?
jcollum
12
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:
Czy możesz wyjaśnić, dlaczego len(list(gen))należy zużywać mniej pamięci niż podejście oparte na redukcji? Pierwsza tworzy nową, listktó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.
normanius
Do Twojej wiadomości: mogę odtworzyć dla Pythona 3.6.8 (na MacBookPro), że metoda 1 przewyższa inne metody pod względem czasu wykonywania (pominąłem metodę 4).
normanius
len(tuple(iterable)) może być jeszcze wydajniejsze: artykuł Nelsona
Minara
9
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).
Iterator w niczym nie przypomina listy połączonej. Obiekt zwrócony z iteratora nie wskazuje na następny obiekt, a obiekty te nie są (koniecznie) przechowywane w pamięci. Raczej może dostarczać obiekty jeden po drugim, w oparciu o jakąkolwiek wewnętrzną logikę (która może, ale nie musi, opierać się na przechowywanej liście).
Tom
1
@Tom Użyłem LinkedList jako przykładu głównie dlatego, że nie wiesz, ile masz, ponieważ wiesz tylko, co jest dalej w pewnym sensie (jeśli jest coś). Przepraszam, jeśli moje sformułowanie wydaje się trochę niewłaściwe lub jeśli zasugerowałem, że są takie same.
Jesus Ramos
8
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.
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.
Powszechną praktyką jest umieszczanie tego typu informacji w nagłówku pliku, a pysam zapewnia do nich dostęp. Nie znam formatu, ale czy sprawdziłeś API?
Jak powiedzieli inni, nie możesz poznać długości z iteratora.
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ą.
Nie narusza niczego i nie ma nic złego w stosowaniu wcześniejszej wiedzy podczas korzystania z iteratora. Wokół jest mnóstwo iteratorów, o których wiadomo, że liczba elementów jest ograniczona. Pomyśl o zwykłym przefiltrowaniu listy, możesz łatwo podać maksymalną długość, po prostu tak naprawdę nie wiesz, ile elementów faktycznie pasuje do warunku filtra. Chęć poznania liczby pasujących do siebie elementów jest poprawną aplikacją, nie naruszającą żadnej mistycznej idei iteratora.
Michael
0
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 copylubdeepcopy
import copy
def get_iter_len(iterator):return sum(1for _ 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ą lenfunkcję 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(1for _ in copy.copy(obj))else:
r = len(obj)finally:passreturn r
Zakresy nie są iteratorami. Istnieje kilka typów iteratorów, które można skopiować, ale inne spowodują niepowodzenie tego kodu z błędem TypeError (np. Generatory), a iteracja przez skopiowany iterator może spowodować dwukrotne wystąpienie efektów ubocznych lub arbitralne uszkodzenie kodu, które, powiedzmy, zwrócił mapiterator oczekujący, że wynikowe wywołania funkcji wystąpią tylko raz.
Odpowiedzi:
Nie. To niemożliwe.
Przykład:
Długość
iterator
jest nieznana, dopóki nie przejdziesz przez nią.źródło
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ć:
Mimo że wykonuje iterację i zlicza każdy element, jest to najszybszy sposób.
Działa również wtedy, gdy iterator nie ma elementu:
Oczywiście dla nieskończonych danych wejściowych działa w nieskończoność, więc pamiętaj, że iteratory mogą być nieskończone:
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.
źródło
_
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ć
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 zrobieniejako inna osoba. Pozwoli to uniknąć utrzymywania go w pamięci.
źródło
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:
(Pythona 3.x wymienić
itertools.izip
zzip
).źródło
sum(1 for _ in iterator)
był prawie dwukrotnie szybszy.zip
sprawach : jeśli zdaszzip(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. :)źródło
__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:
Rzeczywista
count()
realizacja wygląda następująco:źródło
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:
`` ''
1: test_list.py:8: 0,492 KiB
(„list, sec”, 1,9684218849870376)
2: test_list_compr.py:8: 0,867 KiB
(„list_compr, sec”, 2,5885991149989422)
3: suma_testowa.py:8: 0,859 KiB
(„suma, s”, 3,441088170016883)
4: more_itertools / more.py: 413: 1,266 KiB
('ilen, sec', 9.812256851990242)
5: test_reduce.py:8: 0,859 KiB
('zmniejsz, s', 13.436614598002052) ``
Tak więc
len(list(gen))
jest to najczęściej i mniej zużywająca się pamięćźródło
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ł NelsonaIterator 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).
źródło
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.
źródło
Szybki test porównawczy:
Wyniki:
Tzn. Proste count_iter_items jest drogą do zrobienia.
Dostosowywanie tego dla python3:
źródło
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.
źródło
Powszechną praktyką jest umieszczanie tego typu informacji w nagłówku pliku, a pysam zapewnia do nich dostęp. Nie znam formatu, ale czy sprawdziłeś API?
Jak powiedzieli inni, nie możesz poznać długości z iteratora.
źródło
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ą.
źródło
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.
źródło
źródło
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
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:źródło
map
iterator oczekujący, że wynikowe wywołania funkcji wystąpią tylko raz.