2012-05-25 10 views
13

Hier sind zwei Funktionen, die iterierbare Elemente in Unterlisten aufteilen. Ich glaube, dass diese Art von Aufgabe oft programmiert wird. Ich verwende sie, um Protokolldateien zu analysieren, die aus repr Zeilen wie ('Ergebnis', 'Fall', 123, 4.56) und ('Dump', ..) und so weiter bestehen.Kann Ertrag produzieren mehrere aufeinanderfolgende Generatoren?

Ich möchte diese ändern, so dass sie Iteratoren anstelle von Listen ergeben werden. Weil die Liste ziemlich groß werden kann, aber ich kann vielleicht entscheiden, es zu nehmen oder es basierend auf den ersten paar Dingen zu überspringen. Auch wenn die Iter-Version verfügbar ist, möchte ich sie verschachteln, aber mit diesen Listen-Versionen, die etwas Speicher durch das Duplizieren von Teilen verschwenden würden.

Aber mehrere Generatoren aus einer iterierbaren Quelle abzuleiten ist für mich einfach, also bitte ich um Hilfe. Wenn möglich, möchte ich die Einführung neuer Klassen vermeiden.

Auch wenn Sie einen besseren Titel für diese Frage kennen, bitte sagen Sie mir.

Vielen Dank!

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: meine eigene Antwort

auf die Antwort von jeder Dank konnte ich schreiben, was ich gefragt! Für die Funktion "cleave_for_change" könnte ich natürlich auch itertools.groupby verwenden.

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] 

ACHTUNG: Wenn jemand diese verwenden geht, sollten Sie die Generatoren auf jeder Ebene zu Auspuff, wie Andrew darauf hingewiesen. Denn sonst wird die äußere generatorauslösende Schleife genau dort wieder gestartet, wo der innere Generator links ist, anstatt wo der nächste "Block" beginnt.

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

Wenn das, was Sie wollen, ist, eine Liste zu verwerfen, bevor sie zurückgegeben oder sogar bauen, durch einen Filter Argument auf die Funktionen bereitstellt, die möglich wäre. Wenn dieser Filter ein Listenpräfix zurückweist, würde die Funktion die aktuelle Ausgabeliste wegwerfen und das Anhängen an die Ausgabeliste überspringen, bis die nächste Gruppe gestartet wird. –

Antwort

8

Antwort Adams gut ist. dies ist nur, wenn Sie neugierig sind, wie es von Hand zu machen:

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) 

die gibt:

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

(vorherige Version 3, in Python einen Hack oder erforderlich nonlocal, weil ich zugewiesen stream innerhalb generator() was gemacht (eine zweite Variable auch genannt) stream lokal zu generator() standardmäßig - Kredit an gnibbler in den Kommentaren).

Beachten Sie, dass dieser Ansatz gefährlich ist - wenn Sie die generierten Generatoren nicht "konsumieren", werden Sie mehr und mehr erhalten, weil der Stream nicht kleiner wird.

+0

Kein Bedarf für diesen "mutable" Hack. Ändern Sie einfach "stream", anstatt es neu zuzuweisen. Tipp: 'stream.pop (0)' –

+0

oh, verdammt, natürlich. Vielen Dank. –

4

Für Ihre zweite Funktion können Sie itertools.groupby verwenden diese ziemlich leicht zu erreichen.

Hier ist eine alternative Implementierung, die nun Generatoren statt Listen ergibt:

from itertools import groupby 

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

Hier ist es in Aktion (mit liberalem Druck auf dem Weg, so können Sie sehen, was los ist):

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) 

Welche ergibt:

<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

I umgesetzt, was ich beschrieben:

Wenn das, was Sie wollen, ist, eine Liste zu verwerfen, bevor sie zurückgegeben oder sogar zu bauen, durch einen Filter Argument auf die Funktionen bereitstellt, die möglich wäre. Wenn dieser Filter ein Listenpräfix ablehnt, würde die Funktion die aktuelle Ausgabeliste wegwerfen und das Anhängen an die Ausgabeliste überspringen, bis die nächste Gruppe gestartet wird.

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]] 
Verwandte Themen