2015-09-24 10 views
8

Chcę wiedzieć, czy wygenerowana sekwencja ma mniej niż 2 wpisy.Jak poznać wygenerowaną sekwencję jest co najwyżej pewnej długości

>>> def sequence(): 
...  for i in xrange(secret): 
...   yield i 

Moja nieefektywna metoda jest stworzenie listy, i zmierzyć jego długość:

>>> secret = 5 
>>> len(list(sequence())) < 2 
True 

Oczywiście, to zużywa cały generator.

W moim prawdziwym przypadku generator może przechodzić przez dużą sieć. Chcę wykonać test bez zużywania całego generatora lub budowania dużej listy.

Jest recipe in the itertools documentation:

def take(n, iterable): 
    "Return first n items of the iterable as a list" 
    return list(islice(iterable, n)) 

To buduje tylko listę max długości n, która jest lepsza.

Więc mogę powiedzieć:

>>> len(take(2, sequence()) < 2 

Czy jest jeszcze bardziej pythonic, skuteczny sposób to zrobić?

+0

To jest wielkie pytanie Peter Wood - My naiwne podejście byłoby uzyskując wstępną dwa elementy i przechowuj je, ale musi być lepszy sposób. Ciekawi mnie to. Wątpię, by generator mógł poznać swój własny rozmiar, zanim zostanie skonsumowany. –

+0

Silnie niepowiązanych, ale z zasady należy wystrzegać się 'list (iterator)', iteratory mogą być nieskończone ... –

+1

Z pewnością kluczowe pytanie brzmi - jak byś wiedział, * bez * spożywania iteratora, jeśli to będzie wystarczająco długie (lub wystarczająco krótko? Twój tytuł wydaje się być w konflikcie z pytaniem)? Czy masz jakieś inne informacje, które pozwolą ci to ustalić? – jonrsharpe

Odpowiedz

0

rozwiązanie wykorzystujące take wykorzystuje islice, buduje listę i wybija długość to:

>>> from itertools import islice 
>>> len(list(islice(sequence(), 2)) 
2 

Aby uniknąć tworzenia listy możemy użyć sum:

>>> sum(1 for _ in islice(sequence(), 2) 
2 

To trwa około 70% czasu:

>>> timeit('len(list(islice(xrange(1000), 2)))', 'from itertools import islice') 
1.089650974650752 

>>> timeit('sum(1 for _ in islice(xrange(1000), 2))', 'from itertools import islice') 
0.7579448552500647 

owijając go:

>>> def at_most(n, elements): 
...  return sum(1 for _ in islice(elements, n + 1)) <= n 

>>> at_most(5, xrange(5)) 
True 

>>> at_most(2, xrange(5)) 
False 
7

Od wersji 3.4 Pythona generatory mogą implementować length hint. Jeśli generator to zaimplementuje, będzie on dostępny przez object.__length_hint__() method.

Można go przetestować za pomocą urządzenia operator.length_hint() function.

Jeśli jest nie dostępny, jedyną opcją jest, aby spożywać elementy, a korzystanie z take() receptury jest najbardziej skutecznym sposobem, aby to zrobić:

from operator import length_hint 
from itertools import chain 

elements = [] 
length = length_hint(gen, None) 
if length is None: 
    elements = list(take(2, gen)) 
    length = len(elements) 
if length >= 2: 
    # raise an error 
# use elements, then gen 
gen = chain(elements, gen) 
+0

Bardzo interesujące, dziękuję - ciekawi mnie wewnętrzne działanie tej __length_hint __() teraz ... :) –

+0

@ReblochonMasque: wiele generatorów może wstępnie obliczyć długość lub przynajmniej wygenerować oszacowanie. Zależy to jednak całkowicie od generatora. –

+0

Nie wiedziałem tego, dziękuję. –

Powiązane problemy