2010-02-23 15 views
27

Diciamo che ho una funzione di generatore che assomiglia a questo:Index e Slice un generatore in Python

def fib(): 
    x,y = 1,1 
    while True: 
     x, y = y, x+y 
     yield x 

Idealmente, ho potuto solo usare fib() [10] o fib() [2 : 12: 2] per ottenere indici e sezioni, ma al momento devo usare itertools per queste cose. Non posso usare un generatore per una sostituzione in sostituzione di elenchi.

credo che la soluzione sarà quella di avvolgere fib() in una classe:

class Indexable(object): 
    .... 

fib_seq = Indexable(fib()) 

Cosa dovrebbe indicizzabile guarda come per fare questo lavoro?

risposta

33
import itertools 

class Indexable(object): 
    def __init__(self,it): 
     self.it = iter(it) 
    def __iter__(self): 
     return self.it 
    def __getitem__(self,index): 
     try: 
      return next(itertools.islice(self.it,index,index+1)) 
     except TypeError: 
      return list(itertools.islice(self.it,index.start,index.stop,index.step)) 

Si può usare in questo modo:

it = Indexable(fib()) 
print(it[10]) 
#144 
print(it[2:12:2]) 
#[610, 1597, 4181, 10946, 28657] 

noti che it[2:12:2] non restituisce [3, 8, 21, 55, 144] da quando l'iteratore aveva già avanzato 11 elementi a causa della chiamata a it[10].

Edit: Se vuoi it[2:12:2] per tornare [3, 8, 21, 55, 144] allora forse usare questo invece:

class Indexable(object): 

    def __init__(self, it): 
     self.it = iter(it) 
     self.already_computed = [] 

    def __iter__(self): 
     for elt in self.it: 
      self.already_computed.append(elt) 
      yield elt 

    def __getitem__(self, index): 
     try: 
      max_idx = index.stop 
     except AttributeError: 
      max_idx = index 
     n = max_idx - len(self.already_computed) + 1 
     if n > 0: 
      self.already_computed.extend(itertools.islice(self.it, n)) 
     return self.already_computed[index] 

Questa versione salva i risultati in self.already_computed e usa questi risultati se possibile. In caso contrario, calcola più risultati finché non ha un numero sufficiente di per restituire l'elemento o la sezione indicizzata.

+0

Per lo stesso comportamento come una lista, __getitem__ avrebbe bisogno di riavvolgere il generatore. C'è un modo semplice per farlo? –

+0

Non so se ci sia un modo per farlo, facile o no, ma la classe indicizzabile potrebbe semplicemente memorizzare tutti gli elementi già generati in una lista. Quindi '__getitem__' estrarrebbe i numeri direttamente dalla lista, dopo aver fatto avanzare il generatore, se necessario. – MatrixFrog

+0

Grazie MatrixFrog; è quello che ho finito per fare. – unutbu

0

Quindi in base al largo il codice ~ unutbu e aggiungendo in un piccolo itertools.tee:

import itertools 

class Indexable(object): 
    def __init__(self, it): 
     self.it = it 

    def __iter__(self): 
     self.it, cpy = itertools.tee(self.it) 
     return cpy 

    def __getitem__(self, index): 
     self.it, cpy = itertools.tee(self.it) 
     if type(index) is slice: 
      return list(itertools.islice(cpy, index.start, index.stop, index.step)) 
     else: 
      return next(itertools.islice(cpy, index, index+1)) 
+0

Giusto, ma questa opzione brucia la CPU ogni volta che la usi. Per gli elenchi che sono troppo lunghi per essere memorizzati in ~ unutbu's già_computed', è probabile che anche il tempo di CPU qui richiesto sia eccessivo. –

+0

Penso che potrebbe anche venire alla complessità del generatore, se il codice all'interno del generatore è pesante per la CPU usando già_computato è l'opzione migliore ma forse se è semplice come xrange, basta ricominciare è meglio. –

+0

Non itertools.tee internamente memorizza solo i risultati in modo che possano riprodurli in un secondo momento? Non ha modo di sapere se può copiare l'iteratore; considera un iteratore sulle richieste di rete in arrivo o qualcosa del genere. – Ben

0

Se si tratta di una fetta 1-uso di quanto si può semplicemente utilizzare il metodo scritto da ~ unutbu. Se è necessario suddividere più volte, è necessario memorizzare tutti i valori intermedi in modo da poter "riavvolgere" l'iteratore. Dal momento che gli iteratori possono iterare qualsiasi cosa, non avrebbe un metodo di rewind di default.

Inoltre, dal momento che un iteratore riavvolgimento deve memorizzare ogni risultato intermedio sarebbe (nella maggior parte dei casi) non hanno alcun vantaggio rispetto semplicemente facendo list(iterator)

Fondamentalmente ... si sia non hai bisogno di un iteratore, o si' non abbastanza specifico sulla situazione.

+3

'list (fib())' alla fine ti darà un'Errore di Memoria alla fine –

+0

E riguardo il caso in cui voglio il 6 ° numero di Fibonacci. Il modo più chiaro per esprimere questo è fib [6000] anche se ci sono diversi modi per ottenerlo, ma non sono espressi con eleganza. –

+0

Il mio punto è che in quasi tutte le situazioni della vita reale si renderebbe l'oggetto stesso affettabile invece di implementare l'affettatura sul generatore. In caso contrario, sarebbe impossibile ottimizzare correttamente la chiamata di funzione. Per qualsiasi grande numero di fibonacci non si vorrebbe generare l'intero elenco solo per ottenere un numero, si utilizzerà un metodo per "indovinare" il numero. – Wolph

0

Ecco la risposta di ~ unutbu modificata all'elenco delle sottoclassi. Ovviamente l'abuso come append, insert produrrà risultati strani!

si vuole ricevere __str__ e __repr__ metodi gratis se

import itertools 
class Indexable(list): 
    def __init__(self,it): 
     self.it=it 
    def __iter__(self): 
     for elt in self.it: 
      yield elt 
    def __getitem__(self,index): 
     try: 
      max_idx=index.stop 
     except AttributeError: 
      max_idx=index 
     while max_idx>=len(self): 
      self.append(next(self.it)) 
     return list.__getitem__(self,index) 
+1

Le liste sono troppo diverse da quelle che vogliamo perché ciò sia utile. Non farlo. – fletom

+0

Ok, ma chi è "_we_"? –

+3

Le persone che hanno questo problema, suppongo. – fletom