2015-06-03 11 views
16

Nehmen wir an, wir möchten einen Iterator verarbeiten und wollen es durch Chunks behandeln.
Die Logik pro Chunk hängt von zuvor berechneten Chunks ab, so dass groupby() nicht hilft.Wie nicht das nächste Element nach itertools.takewhile() zu verpassen

in diesem Fall unser Freund ist itertools.takewhile():

while True: 
    chunk = itertools.takewhile(getNewChunkLogic(), myIterator) 
    process(chunk) 

Das Problem ist, dass takewhile() Bedürfnisse nach dem letzten Elemente gehen, die die neue Chunk Logik erfüllt, so dass das erste Element ‚Essen‘ für der nächste Brocken.

Es gibt verschiedene Lösungen an, dass, einschließlich Verpackung oder à la C der ungetc(), etc ..
Meine Frage ist: Gibt es eine elegante Lösung?

+0

Holen Sie sich Cython und erstellen Sie Ihre eigenen –

Antwort

9

takewhile() muss in der Tat das nächste Element betrachten, um zu bestimmen, wann das Verhalten umgeschaltet werden soll.

könnten Sie einen Wrapper verwenden, die das letzte Mal gesehen Element verfolgt, und das kann ‚Reset‘, ein Element zu sichern:

_sentinel = object() 

class OneStepBuffered(object): 
    def __init__(self, it): 
     self._it = iter(it) 
     self._last = _sentinel 
     self._next = _sentinel 
    def __iter__(self): 
     return self 
    def __next__(self): 
     if self._next is not _sentinel: 
      next_val, self._next = self._next, _sentinel 
      return next_val 
     try: 
      self._last = next(self._it) 
      return self._last 
     except StopIteration: 
      self._last = self._next = _sentinel 
      raise 
    next = __next__ # Python 2 compatibility 
    def step_back(self): 
     if self._last is _sentinel: 
      raise ValueError("Can't back up a step") 
     self._next, self._last = self._last, _sentinel 

Wickeln Sie Ihren Iterator in dieser eine, bevor es mit takewhile() mit:

myIterator = OneStepBuffered(myIterator) 
while True: 
    chunk = itertools.takewhile(getNewChunkLogic(), myIterator) 
    process(chunk) 
    myIterator.step_back() 

Demo:

>>> from itertools import takewhile 
>>> test_list = range(10) 
>>> iterator = OneStepBuffered(test_list) 
>>> list(takewhile(lambda i: i < 5, iterator)) 
[0, 1, 2, 3, 4] 
>>> iterator.step_back() 
>>> list(iterator) 
[5, 6, 7, 8, 9] 
+0

@KarolyHorvath: Es hängt davon ab, wie Ihr ursprünglicher Iterator wurde codiert, wenn die Leistung getötet wird. Wenn alles andere in C codiert wäre, dann fügt das einen Schritt zurück in den Python-Interpreter ein und das kann die Leistung beeinträchtigen. Die Alternative besteht darin, Ihren Algorithmus neu zu programmieren, damit er sich nicht auf 'takewhile()' verlässt. Ohne Details darüber, was du tust, kann ich dir an dieser Stelle nicht helfen. –

+0

@MartijnPieters: Ja, das habe ich gemeint. Ich mache nichts, das OP ist jemand anderes;) Was den 'ValueError' angeht: Es macht vollkommen Sinn aus der Sicht von 'OneStepBuffered', aber für OPs Aufgabe sieht das wie ein Fehler für mich aus. –

+0

Nun, vorausgesetzt, es ist ein gültiges Szenario .... Hinweis: Sie können die Ausnahme abfangen, wenn das der Fall ist –

1

Angesichts der abrufbaren GetNewChunkLogic() wird danach True entlang des ersten Stücks und False berichten.
folgenden Ausschnitt

  1. die 'zusätzlichen nächsten Schritt' Problem der takewhile löst.
  2. ist elegant, weil Sie die Back-One-Step-Logik nicht implementieren müssen.

def partition(pred, iterable): 
    'Use a predicate to partition entries into true entries and false entries' 
    # partition(is_odd, range(10)) --> 1 3 5 7 9 and 0 2 4 6 8 
    t1, t2 = tee(iterable) 
    return filter(pred, t1), filterfalse(pred, t2) 

while True: 
    head, tail = partition(GetNewChunkLogic(), myIterator) 
    process(head) 
    myIterator = tail 

jedoch die eleganteste Art und Weise ist Ihr GetNewChunkLogic in einen Generator zu ändern und die while Schleife entfernen.

Verwandte Themen