2012-05-25 10 views
13

Qui ci sono due funzioni che dividono gli articoli iterabili in sotto-liste. Credo che questo tipo di compito sia programmato molte volte. Li utilizzo per analizzare i file di registro costituiti da righe repr come ('risultato', 'caso', 123, 4.56) e ('dump', ..) e così via.È possibile produrre più generatori consecutivi?

Vorrei cambiare questi in modo che producano iteratori anziché elenchi. Perché l'elenco potrebbe diventare abbastanza grande, ma potrei essere in grado di decidere di prenderlo o saltarlo in base ai primi pochi elementi. Inoltre, se la versione iter è disponibile, vorrei accoppiarli, ma con queste versioni di elenchi che sprecherebbero un po 'di memoria duplicando parti.

Ma non è facile per me generare più generatori da una fonte iterabile, quindi chiedo aiuto. Se possibile, desidero evitare di introdurre nuove classi.

Inoltre, se si conosce un titolo migliore per questa domanda, per favore dimmi.

Grazie!

def cleave_by_mark (stream, key_fn, end_with_mark=False): 
    '''[f f t][t][f f] (true) [f f][t][t f f](false)''' 
    buf = [] 
    for item in stream: 
     if key_fn(item): 
      if end_with_mark: buf.append(item) 
      if buf: yield buf 
      buf = [] 
      if end_with_mark: continue 
     buf.append(item) 
    if buf: yield buf 

def cleave_by_change (stream, key_fn): 
    '''[1 1 1][2 2][3][2 2 2 2]''' 
    prev = None 
    buf = [] 
    for item in stream: 
     iden = key_fn(item) 
     if prev is None: prev = iden 
     if prev != iden: 
      yield buf 
      buf = [] 
      prev = iden 
     buf.append(item) 
    if buf: yield buf 

edit: la mia risposta personale

Grazie alla risposta di tutti, ho potuto scrivere quello che ho chiesto! Ovviamente, come per la funzione "cleave_for_change", potrei usare anche itertools.groupby.

def cleave_by_mark (stream, key_fn, end_with_mark=False): 
    hand = [] 
    def gen(): 
     key = key_fn(hand[0]) 
     yield hand.pop(0) 
     while 1: 
      if end_with_mark and key: break 
      hand.append(stream.next()) 
      key = key_fn(hand[0]) 
      if (not end_with_mark) and key: break 
      yield hand.pop(0) 
    while 1: 
     # allow StopIteration in the main loop 
     if not hand: hand.append(stream.next()) 
     yield gen() 

for cl in cleave_by_mark (iter((1,0,0,1,1,0)), lambda x:x): 
    print list(cl), # start with 1 
# -> [1, 0, 0] [1] [1, 0] 
for cl in cleave_by_mark (iter((0,1,0,0,1,1,0)), lambda x:x): 
    print list(cl), 
# -> [0] [1, 0, 0] [1] [1, 0] 
for cl in cleave_by_mark (iter((1,0,0,1,1,0)), lambda x:x, True): 
    print list(cl), # end with 1 
# -> [1] [0, 0, 1] [1] [0] 
for cl in cleave_by_mark (iter((0,1,0,0,1,1,0)), lambda x:x, True): 
    print list(cl), 
# -> [0, 1] [0, 0, 1] [1] [0] 

/

def cleave_by_change (stream, key_fn): 
    '''[1 1 1][2 2][3][2 2 2 2]''' 
    hand = [] 
    def gen(): 
     headkey = key_fn(hand[0]) 
     yield hand.pop(0) 
     while 1: 
      hand.append(stream.next()) 
      key = key_fn(hand[0]) 
      if key != headkey: break 
      yield hand.pop(0) 
    while 1: 
     # allow StopIteration in the main loop 
     if not hand: hand.append(stream.next()) 
     yield gen() 

for cl in cleave_by_change (iter((1,1,1,2,2,2,3,2)), lambda x:x): 
    print list(cl), 
# -> [1, 1, 1] [2, 2, 2] [3] [2] 

ATTENZIONE: Se qualcuno sta andando a utilizzare questi, assicurati di scarico generatori a tutti i livelli, come Andrew ha sottolineato. Perché altrimenti il ​​ciclo di generazione del generatore esterno si riavvierà proprio dove il generatore interno è rimasto invece di dove inizia il prossimo "blocco".

stream = itertools.product('abc','1234', 'ABCD') 
for a in iters.cleave_by_change(stream, lambda x:x[0]): 
    for b in iters.cleave_by_change(a, lambda x:x[1]): 
     print b.next() 
     for sink in b: pass 
    for sink in a: pass 

('a', '1', 'A') 
('b', '1', 'A') 
('c', '1', 'A') 
+0

Se quello che vuoi è quello di respingere una lista prima di essere restituita o addirittura costruire, fornendo un argomento filtro per le funzioni che sarebbero possibili. Quando questo filtro rifiuta un prefisso di elenco, la funzione eliminerà l'elenco di output corrente e salterà l'aggiunta all'elenco di output fino all'avvio del gruppo successivo. –

risposta

8

di Adam è buona. questo è solo nel caso in cui siete curiosi come farlo a mano:

def cleave_by_change(stream): 
    def generator(): 
     head = stream[0] 
     while stream and stream[0] == head: 
      yield stream.pop(0) 
    while stream: 
     yield generator() 

for g in cleave_by_change([1,1,1,2,2,3,2,2,2,2]): 
    print list(g) 

che dà:

[1, 1, 1] 
[2, 2] 
[3] 
[2, 2, 2, 2] 

(versione precedente richiesto un hack o, in Python 3, nonlocal perché ho assegnato a stream all'interno di generator() che ha reso (una seconda variabile anche chiamata) stream locale a generator() per impostazione predefinita - credito a gnibbler nei commenti).

notare che questo approccio è pericoloso: se non si "consumano" i generatori restituiti, si otterrà sempre di più, poiché lo stream non diventa più piccolo.

+0

Non c'è bisogno di questo hack "mutabile". Muta semplicemente 'stream' invece di riassegnarlo. Suggerimento: 'stream.pop (0)' –

+0

oh, dannazione, ovviamente. Grazie. –

4

Per la vostra seconda funzione, è possibile utilizzare itertools.groupby per raggiungere questo abbastanza facilmente.

Ecco un'implementazione alternativa che oggi produce generatori invece di liste:

from itertools import groupby 

def cleave_by_change2(stream, key_fn): 
    return (group for key, group in groupby(stream, key_fn)) 

Ecco in azione (con la stampa liberale lungo la strada, in modo da poter vedere cosa sta succedendo):

main_gen = cleave_by_change2([1,1,1,2,2,3,2,2,2,2], lambda x: x) 

print main_gen 

for sub_gen in main_gen: 
    print sub_gen 
    print list(sub_gen) 

che produce: risposta

<generator object <genexpr> at 0x7f17c7727e60> 
<itertools._grouper object at 0x7f17c77247d0> 
[1, 1, 1] 
<itertools._grouper object at 0x7f17c7724850> 
[2, 2] 
<itertools._grouper object at 0x7f17c77247d0> 
[3] 
<itertools._grouper object at 0x7f17c7724850> 
[2, 2, 2, 2] 
1

ho implementato quello che ho descritto:

Se quello che vuoi è quello di respingere una lista prima che venga restituito o addirittura costruire, fornendo un argomento filtro per le funzioni che sarebbero possibile. Quando questo filtro rifiuta un prefisso di elenco, la funzione dovrebbe scartare l'elenco di output corrente e saltare l'aggiunta all'elenco di output fino all'avvio del gruppo successivo.

def cleave_by_change (stream, key_fn, filter=None): 
    '''[1 1 1][2 2][3][2 2 2 2]''' 
    S = object() 
    skip = False 
    prev = S 
    buf = [] 
    for item in stream: 
     iden = key_fn(item) 
     if prev is S: 
      prev = iden 
     if prev != iden: 
      if not skip: 
       yield buf 
      buf = [] 
      prev = iden 
      skip = False 
     if not skip and filter is not None: 
      skip = not filter(item) 
     if not skip: 
      buf.append(item) 
    if buf: yield buf 

print list(cleave_by_change([1, 1, 1, 2, 2, 3, 2, 2, 2, 2], lambda a: a, lambda i: i != 2)) 
# => [[1, 1, 1], [3]] 
print list(cleave_by_change([1, 1, 1, 2, 2, 3, 2, 2, 2, 2], lambda a: a, lambda i: i == 2)) 
# => [[2, 2], [2, 2, 2, 2]]