2012-05-25 5 views
13

反復可能な項目をサブリストに分割する2つの関数があります。私はこのタイプの仕事は何度もプログラムされていると信じています。私はそれらを使用して( 'result'、 'case'、123、4.56)と( 'dump'、..)のようなrepr行からなるログファイルを解析します。複数の連続した発電機を生産することができますか?

リストではなくイテレータを生成するようにこれらを変更したいと思います。リストはかなり大きくなるかもしれませんが、私はそれを取るか、最初のいくつかの項目に基づいてスキップすることができます。また、iterバージョンが利用可能な場合は、それらをネストしたいと思いますが、これらのリストバージョンでは、部分を複製することによっていくらかのメモリを無駄にするでしょう。

しかし、反復可能なソースから複数のジェネレータを派生させるのは簡単ではないので、私は助けを求めます。可能であれば、私は新しいクラスの導入を避けたいと考えています。

また、この質問のより良いタイトルが分かっている場合は教えてください。

ありがとうございました!

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 

編集:私自身の答えは皆の答えに

おかげで、私は私が尋ね書くことができます!もちろん、「cleave_for_change」機能については、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] 

注意:誰もがこれらを使用するために起こって場合アンドリューが指摘したように、排気あらゆるレベルでの発電機に確認してください。そうでなければ、外側のジェネレータ降伏ループは、次の「ブロック」が始まるのではなく、内側のジェネレータが残っている場所ですぐに再スタートするからです。

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

何をしたいことは可能であろう機能にフィルタ引数を提供することで、それが返される前に、リストを拒否したり、構築することである場合。このフィルタがリストプレフィックスを拒否すると、関数は現在の出力リストを投げ捨て、次のグループが開始されるまで出力リストに追加をスキップします。 –

答えて

8

アダムの答えは良いです。私はに割り当てられているため

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

(以前のバージョンのpython 3にハックまたは、必要に応じ、nonlocal

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) 

できます:あなたが手でそれを行う方法を興味が念のために、これはありますstreamgenerator()内部では、streamはデフォルトでgenerator()にローカルになっています(コメントではgnibblerへのクレジット)。

この方法は危険です。ストリームがそれほど小さくなっていないため、返されるジェネレータを「消費」しないと、ますます多くなります。

+0

この 'mutable'ハックは必要ありません。それを再割り当てする代わりに 'stream'を変更するだけです。ヒント: 'stream.pop(0)' –

+0

ああ、まあ、もちろん。ありがとう。 –

4

2番目の機能では、itertools.groupbyを使用すると、これを簡単に実行できます。 (あなたは何が起こっているのを見ることができるように道に沿ってリベラルな印刷で、)ここで

from itertools import groupby 

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

それはアクションである:

ここで今、発電機の代わりに、リストを生成する別の実装です

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) 

得られます

<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

私が説明したものを実装: ことは可能であろう機能にフィルタ引数を提供することで、ビルドをあなたがしたいことは、それが返される前に、リストを拒否している場合

を、あるいは 。このフィルタがリストプレフィックスを拒否すると、関数は は現在の出力リストを投げ捨て、出力リスト を次のグループが開始されるまで追加しないでスキップします。

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]] 
関連する問題