2009-01-07 5 views
12

Ich lerne Python, und ich habe eine Situation, in der ich Elemente von einem Iterator konsumieren möchte. Der schwierige Teil ist, dass ich unter bestimmten Bedingungen "un-iterieren" möchte. Das bedeutet, dass ein Element vor der Schleife wieder auf die Vorderseite des Iterators gesetzt wird.Pythonic Äquivalent von Unshift oder Redo?

Nehmen wir zum Beispiel an, ich pflücke Äpfel von einem Baum. Mein Obstkorb kann nur 10kg fassen, bevor er entleert werden muss. Aber ich muss jeden Apfel pflücken, bevor ich ihn wiegen kann und feststellen, ob dieser Apfel die Kapazität des Korbs übersteigen würde.

In einer Sprache wie Perl, könnte ich den Apfel zurück auf den Baum, unshift() und dann die Schleife Ausdruck lassen den Apfel wieder holen:

while ($apple = shift(@tree)) { 
    $wt = weight($apple); 
    if ($wt + weight(@basket) > 10) { 
    send(@basket); 
    @basket =(); 
    unshift(@tree, $apple); 
    } else { 
    push(@basket, $element); 
    } 
} 

Oder kann ich auch redo verwenden, die Verarbeitung wird fortgesetzt an der Spitze des Blocks, ohne den Schleifenausdruck zu bewerten. So kann derselbe Apfel nach dem Entleeren des Korbes erneut verarbeitet werden.

while ($apple = shift(@tree)) { 
    $wt = weight($apple); 
    if ($wt + weight(@basket) > 10) { 
    send(@basket); 
    @basket =(); 
    redo; 
    } else { 
    push(@basket, $apple); 
    } 
} 

Was wäre die pythonic Lösung für diese Art von Problem?

+0

wenn $ wt> 10, dann gibt es eine unendliche Schleife (das erste Beispiel den gesamten Speicher isst, hält die zweite gerade nie). – jfs

+0

@ J.F .: Du hast Recht, aber in diesem Fall kann man davon ausgehen, dass kein einzelner Apfel 10kg überschreiten wird. –

Antwort

13

Warum sollte man sich mit dem Verschieben beschäftigen, wenn die else-Klausel immer auftreten sollte?

Wie auch immer, ich bin mir ziemlich sicher, dass Python nicht das Verhalten hat, nach dem Sie suchen.

+1

Nur um zu verdeutlichen: Pythons * eingebaute Iteratoren * haben nicht die Art von Verhalten, nach der er sucht. – cdleary

+0

Ich habe den Codestil geändert. Fühlen Sie sich frei, zurückzurollen. – jfs

+0

Dies scheint die einfachste Lösung zu sein. Vielen Dank! –

-2

Es gibt keine allgemeine Möglichkeit, einen Wert in einen Iterator in Python zu pushen. Ein Stack oder eine verkettete Liste ist dafür besser geeignet.

Wenn Sie über eine Liste oder etwas iterieren, können Sie das Element natürlich manuell zur Liste hinzufügen. Sie können aber auch über Objekte iterieren, die nicht so manipuliert werden können.

Wenn Sie mit Python diesen Algorithmus implementieren möchten, müssen Sie eine Datenstruktur auswählen, die die gewünschten Operationen zulässt. Ich schlage die Methoden .push() und .pop() vor, mit denen Sie Listen als Stapel behandeln können.

16

Ich lerne Python, und ich habe eine Situation, in der ich Elemente von einem Iterator konsumieren möchte. Der schwierige Teil ist, dass ich unter bestimmten Bedingungen "un-iterieren" möchte. Das bedeutet, dass ein Element vor der Schleife wieder auf die Vorderseite des Iterators gesetzt wird.

Hier ist eine einfache Lösung:

class MyIterator(object): # undo-able iterator wrapper 
    def __init__(self, iterable): 
     super(MyIterator, self).__init__() 
     self.iterator = iter(iterable) 
     self.stack = [] 

    def __iter__(self): 
     return self 

    def next(self): 
     if self.stack: 
      return self.stack.pop() 
     return self.iterator.next() # Raises StopIteration eventually 

    def undo(self, item): 
     self.stack.append(item) 
for i in MyIterator(xrange(5)): print i 
0 
1 
2 
3 
4 
rng = MyIterator(xrange(5)) 
rng.next() 
0 
rng.next() 
1 
rng.undo(1) 
rng.next() 
1 
+0

Danke, das beantwortet meine ursprüngliche Frage, wie man eine Unshift-ähnliche Operation implementieren könnte. –

1

Während ich dieses @Patrick Schreiben wurde bereits die gleiche Sache vorgeschlagen. Aber da ich es geschrieben habe, werde ich den Code sowieso einfügen, mit Kommentaren in Code-Markierungsmethoden von Patrick.

import random 

apples=[random.randint(1,3) for j in range(10)] 
print 'apples',apples 

basket=[] 
y=6 
baskets=[] 

for i in range(len(apples)): 
    if sum(basket+[apples[i]])>y: 
     #basket is full                                  
     baskets.append(basket)#basket.send()                             
     basket=[]#basket.empty()                                
    basket.append(apples[i])#add apple to basket                            

print 'baskets',baskets 

obwohl dies nicht pop() die Äpfel aus dem ursprünglichen Iterator. Bitte beachten Sie, dass dies auch ein gewünschtes Verhalten ist.

der Ausgang

apples [1, 1, 3, 3, 1, 1, 3, 3, 2, 3] 
baskets [[1, 1, 3], [3, 1, 1], [3, 3]] 
+0

Danke für das Beispiel! –

6

Änderungen an seinen internen Zustand über die Methode send() empfangen kann, würde ich sagen dass the most Pythonic solution is the simplest one. Anstatt zu versuchen, einen Iterator in einen Generator-Ausdruck zu packen, der es Ihnen ermöglicht, "zurückzuspulen" oder etwas ähnlich komplexes, verwenden Sie eine while-Schleife, wie Sie es in Perl getan haben! Iterators don't mix very nicely with mutation, anywho.

Einfache Übersetzung Ihrer Implementierung (ohne Berücksichtigung @Patrick 's-Optimierung):

while tree: 
    apple = tree.pop(0) 
    if apple.weight + basket.weight > 10: 
     basket.send() 
     basket.clear() 
     tree.insert(0, apple) # Put it back. 
    else: 
     basket.append(apple) 

Oder Sie peek -ähnlichen Funktionalität mit geordneten Sequenz Indizes verwenden:

while tree: 
    apple = tree[0] # Take a peek at it. 
    if apple.weight + basket.weight > 10: 
     basket.send() 
     basket.clear() 
    else: 
     basket.append(tree.pop(0)) 

Wenn Sie don' t wie das "einfache" Argument, schau dir die collections.deque Iteratoren an, die im obigen (verlinkten) Thread erwähnt werden.

+1

Danke, es ist gut, daran zu denken, von dem Problem zurückzutreten. Anstatt sich auf einen Mechanismus wie Unshift zu konzentrieren, ist es am besten, das wahre Problem auf eine einfachere Weise zu lösen. –

4

Wenn Sie wollen nicht den Vorschlag der anderen folgen nur die else-Klausel zu entfernen, können Sie Ihre eigenen unshift Funktion schreiben, die in ähnlicher Weise wie in Perl mit jedem iterable arbeiten: Dann

class UnshiftableIterable(object): 
    def __init__(self, iterable): 
     self._iter = iter(iterable) 
     self._unshifted = [] # empty list of unshifted stuff 
    def __iter__(self): 
     while True: 
      if self._unshifted: 
       yield self._unshifted.pop() 
      else: 
       yield self._iter.next() 
    def unshift(self, item): 
     self._unshifted.append(item) 

im Code:

it = UnshiftableIterable(tree) 
for apple in tree: 
    if weigth(basket) + weight(apple) > MAX_WEIGHT: 
     send(basket) 
     basket = [] 
     it.unshift(apple) 
    else: 
     basket.append(apple) 

Einige Tests der UnshiftableIterable:

it = UnshiftableIterable(xrange(5)) 

for i in it: 
    print '*', 
    if i == 2: 
     it.unshift(10) 
    else: 
     print i, 
# output: * 0 * 1 * * 10 * 3 * 4 
+0

Ihr 'UnshiftableIterator' ist kein Iterator (er hat keine' next() 'Methode). Es ist iterierbar (es hat '__iter __()' Methode). – jfs

+0

@ J.F.Sebastian: True. Geänderter Name, um das zu reflektieren. – nosklo

+0

'für Apfel im Baum:' -> 'für Apfel darin:'. Andernfalls werden unverschobene Werte niemals verwendet. – jfs

0

By the way, was Sie wirklich wollen, ist list.insert (0, yourObject)

0

Zurück zur ursprünglichen Frage zu impementing unshift kann operator.delitem verwendet werden, um eine einfache, nicht-OO-Funktion zu implementieren:

from operator import delitem 

def unshift(l,idx): 
    retval = l[0] 
    delitem(l,0) 
    return retval 

x = [2,4,6,8] 

firstval = unshift(x,0) 

print firstval,x 

2 [4, 6, 8]

+0

Das ist nicht unangeschlossen - das ist Verschiebung. –