2010-02-23 14 views
27

Lets sagen, dass ich eine Generatorfunktion haben, die wie folgt aussieht:Index und Scheibe ein Generator in Python

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

Ideal nur fib() verwenden, könnte ich [10] oder fib() [2 : 12: 2] um Indizes und Slices zu bekommen, aber momentan muss ich iTertools für diese Dinge benutzen. Ich kann keinen Generator als Ersatz für Listen verwenden.

Ich glaube, die Lösung wird fib() in einer Klasse zu wickeln sein:

class Indexable(object): 
    .... 

fib_seq = Indexable(fib()) 

Was sollte Indizierbare aussehen wie machen diese Arbeit?

Antwort

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

Man könnte es wie folgt verwenden:

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

Beachten Sie, dass it[2:12:2] nicht [3, 8, 21, 55, 144] zurückkehrt, da der Iterator bereits 11 Elemente vorgedrungen, weil der Aufruf von it[10].

Edit: Wenn Sie it[2:12:2] möchten [3, 8, 21, 55, 144] dann vielleicht diese zurückkehren statt:

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] 

Diese Version speichert die Ergebnisse in self.already_computed und verwendet diese Ergebnisse wenn möglich. Andernfalls berechnet es mehr Ergebnisse, bis es ausreichend viele hat, um das indizierte Element oder die Scheibe zurückzugeben.

+0

das gleiche Verhalten wie einer Liste zu zeigen, würde __getitem__ müssen den Generator zurückzuspulen. Gibt es einen einfachen Weg, das zu tun? –

+0

Ich weiß nicht, ob es eine Möglichkeit gibt, dies zu tun, einfach oder nicht, aber die indexierbare Klasse könnte einfach alle bereits generierten Elemente in einer Liste speichern. Dann würde "__getitem__" die Zahlen direkt aus der Liste ziehen, nachdem der Generator bei Bedarf zuerst vorgeschoben wurde. – MatrixFrog

+0

Danke MatrixFrog; das ist, was ich getan habe. – unutbu

0

Also den Code aus ~ unutbu und das Hinzufügen in einem kleinen itertools.tee beruhte:

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

Richtig, aber diese Option brennt CPU jedes Mal, wenn Sie es verwenden. Für Listen, die zu lang sind, um in ~ unutbu's 'bereits_berechnet' gespeichert zu werden, ist die CPU-Zeit, die hier benötigt wird, wahrscheinlich ebenfalls zu hoch. –

+0

Ich denke, es könnte auch auf die Komplexität des Generators herunterkommen, wenn der Code innerhalb des Generators CPU-lastig ist, wobei beyond_computed die beste Option ist, aber vielleicht ist es einfacher, wenn xrange es einfach neu berechnet. –

+0

Speichert intern itetools.tee nicht nur Ergebnisse, damit sie später wieder abgespielt werden können? Es hat keine Möglichkeit zu wissen, ob es den Iterator kopieren kann; Betrachten Sie einen Iterator über eingehende Netzwerkanforderungen oder so. – Ben

0

Wenn es eine 1-use Scheibe ist, als Sie einfach die Methode durch ~ unutbu geschrieben nutzen könnten. Wenn Sie mehrere Schichten aufteilen müssen, müssen Sie alle Zwischenwerte speichern, damit Sie den Iterator "zurückspulen" können. Da Iteratoren alles iterieren können, würde es standardmäßig keine Rückspulmethode haben.

Da auch ein Zurückspulen Iterator jedes Zwischenergebnis, es würde zu speichern hat (in den meisten Fällen) haben keinen Vorteil gegenüber einfach list(iterator)

Im Grunde tun ... Sie entweder keinen Iterator brauchen, oder Sie‘ Re nicht spezifisch genug über die Situation.

+3

'Liste (fib())' wird Ihnen einen MemoryError schließlich –

+0

Wie wäre es mit dem Fall, wo ich die 6000. Fibonacci-Nummer. Die klarste Art, dies auszudrücken, ist fib [6000], obwohl es mehrere Wege gibt, sie zu erhalten, sie werden einfach nicht so elegant ausgedrückt. –

+0

Mein Punkt ist, dass Sie in fast allen Situationen des realen Lebens das Objekt selbst schnittfähig machen würden, anstatt Slicing am Generator zu implementieren. Wenn nicht, wäre es unmöglich, den Funktionsaufruf richtig zu optimieren. Für jede große Fibonacci-Nummer möchten Sie nicht die gesamte Liste generieren, nur um eine Nummer zu erhalten, Sie würden eine Methode verwenden, um die Zahl zu "erraten". – Wolph

0

Hier ~ Antwort des unutbu zu Unterklasse Liste geändert. Offensichtlich wird Missbrauch wie append, insert etc. seltsame Ergebnisse produzieren!

erhalten Sie tun __str__ und __repr__ Methoden kostenlos obwohl

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

Listen sind zu verschieden von dem, was wir wollen, damit dies nützlich ist. Tu das nicht. – fletom

+0

Ok, aber wer ist "_we_"? –

+3

Leute, die dieses Problem haben, nehme ich an. – fletom