2012-05-25 14 views
13

Oto dwie funkcje, które dzielą elementy iterowalne na listy podrzędne. Wierzę, że ten typ zadania jest programowany wiele razy. Używam ich do analizowania plików dziennika, które składają się z linii repr, takich jak ("wynik", "case", 123, 4.56) i ("dump", ..) i tak dalej.Czy wydajność może generować wiele kolejnych generatorów?

Chciałbym je zmienić, aby były raczej iteratorami niż listami. Ponieważ lista może być dość duża, ale mogę zdecydować się ją przyjąć lub pominąć na podstawie kilku pierwszych elementów. Ponadto, jeśli wersja iter jest dostępna, chciałbym je zagnieździć, ale z tymi wersjami list, które mogłyby zmarnować część pamięci przez powielenie części.

Ale wyprowadzenie wielu generatorów z iterowanego źródła byłoby dla mnie łatwe, więc proszę o pomoc. Jeśli to możliwe, chcę uniknąć wprowadzania nowych zajęć.

Ponadto, jeśli znasz lepszy tytuł dla tego pytania, proszę powiedz mi.

Dziękujemy!

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: moja własna odpowiedź

Dzięki wszystkim za odpowiedzi, mogę napisać co prosiłem! Oczywiście, jeśli chodzi o funkcję "cleave_for_change", mógłbym także użyć 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] 

UWAGA: Jeśli ktoś zamierza z nich korzystać, należy spalin generatorów na każdym poziomie, jak Andrew zauważył. Ponieważ w przeciwnym razie zewnętrzna pętla generująca generator zostanie ponownie uruchomiona w miejscu, w którym pozostawiono generator wewnętrzny zamiast miejsca, w którym zaczyna się następny "blok".

+0

Jeśli to, co chcesz, aby odrzucić listę zanim zostanie zwrócone lub nawet budować, dostarczając argumentu filtra do funkcji, które byłyby możliwe. Kiedy ten filtr odrzuci prefiks listy, funkcja rzuciłaby bieżącą listę wyników i pominęła dodawanie do listy wyjściowej, dopóki nie zostanie uruchomiona następna grupa. –

Odpowiedz

8

Adama jest dobra. to jest tylko w przypadku jesteś ciekawy jak to zrobić ręcznie:

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) 

co daje:

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

(poprzednia wersja wymagana hack lub, w Pythonie 3 nonlocal bo przypisany stream wewnątrz generator(), która dokonała (druga zmienna również nazywana) stream lokalnie do generator() domyślnie - kredyt dla gnibbler w komentarzach).

zauważ, że takie podejście jest niebezpieczne - jeśli nie "zużyjesz" generowanych zwrotów, otrzymasz coraz więcej, ponieważ strumień nie będzie coraz mniejszy.

+0

Nie ma potrzeby stosowania tego "zmiennego" hacka. Po prostu zmutuj 'stream' zamiast go ponownie przypisywać. Podpowiedź: 'stream.pop (0)' –

+0

o, cholera, oczywiście. dzięki. –

4

Dla twojej drugiej funkcji, możesz użyć itertools.groupby, aby osiągnąć ten cel dość łatwo.

Oto alternatywna implementacja że teraz daje generatory zamiast list:

from itertools import groupby 

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

Oto ona w działaniu (z drukowaniem liberalnej po drodze, dzięki czemu można zobaczyć, co się dzieje):

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) 

co daje: odpowiedź

<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 wdrożone co opisałem:

Jeśli to, co chcesz, aby odrzucić listę zanim zostanie zwrócone lub nawet build, dostarczając argumentu filtra do funkcji, które byłyby możliwe. Gdy ten filtr odrzuci prefiks listy, funkcja będzie podrzucać bieżącą listę wyjściową i pomijać dołączanie do listy wyjściowej do momentu rozpoczęcia następnej grupy.

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