2015-06-03 8 views
16

Supponiamo di voler elaborare un iteratore e di gestirlo in blocchi.
La logica per blocco dipende da blocchi calcolati in precedenza, pertanto groupby() non è di aiuto.Come non perdere l'elemento successivo dopo itertools.takewhile()

Il nostro amico in questo caso è itertools.takewhile():

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

Il problema è che takewhile() ha bisogno di andare oltre l'ultimo elemento che soddisfa la nuova logica pezzo, quindi 'mangiare' il primo elemento per il il prossimo pezzo.

Ci sono varie soluzioni a questo, comprese le confezioni o un di ungetc() la C, ecc ..
La mia domanda è: esiste una soluzione elegante ?

+0

scarica cython e crea il tuo –

risposta

9

takewhile() in effetti ha bisogno di esaminare l'elemento successivo per determinare quando attivare o disattivare il comportamento.

si potrebbe usare un involucro che traccia l'ultimo elemento visto, e che può essere 'reset' per eseguire il backup un elemento:

_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 

avvolgere l'iteratore in questo uno prima di utilizzarlo con takewhile():

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: dipende da come è stato codificato il tuo iteratore originale se le prestazioni vengono interrotte. Se tutto il resto è stato codificato in C, allora sì, questo aggiunge un passo indietro all'interprete Python e questo può influenzare le prestazioni. L'alternativa è riorganizzare il tuo algoritmo per non fare affidamento su 'takewhile()'. Senza dettagli su ciò che stai facendo non è qualcosa a cui posso aiutarti a questo punto. –

+0

@MartijnPieters: sì, questo è quello che intendevo. Non sto facendo nulla, l'OP è qualcun altro;) Per quanto riguarda il 'ValueError': ha perfettamente senso dal punto di vista di' OneStepBuffered', ma per il compito di OP, sembra un bug per me. –

+0

beh, supponendo che sia uno scenario valido .... nota: puoi notare l'eccezione se questo è il caso –

1

Dato il callable GetNewChunkLogic() riporterà True lungo il primo blocco e False in seguito.
Il seguente frammento

  1. risolve il problema 'ulteriore passaggio successivo' di takewhile.
  2. è elegante perché non è necessario implementare la logica back-one-step.

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 

Tuttavia, il modo più elegante è quello di modificare la GetNewChunkLogic in un generatore e rimuovere il loop while.

Problemi correlati