13

反復可能なアイテムをサブリストに分割する 2 つの関数を次に示します。このタイプのタスクは何度もプログラムされていると思います。reprこれらを使用して、('result', 'case', 123, 4.56) や ('dump', ..) などの行で構成されるログ ファイルを解析します。

リストではなくイテレータを生成するようにこれらを変更したいと思います。リストはかなり大きくなるかもしれませんが、最初のいくつかの項目に基づいて、それを取るかスキ​​ップするかを決めることができるかもしれません. また、 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]

注意:誰かがこれらを使用する場合は、Andrew が指摘したように、すべてのレベルで発電機を使い果たすようにしてください。そうしないと、外側のジェネレーター生成ループが、次の「ブロック」が始まる場所ではなく、内側のジェネレーターが去った場所から再開されるためです。

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')
4

3 に答える 3

8

アダムの答えは良いです。これは、手動で行う方法に興味がある場合に備えてです。

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)

与える:

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

(以前のバージョンではハックが必要でした。または、Python 3 では、デフォルトでローカルに(2 番目の変数も呼び出された)内部にnonlocal割り当てられたため、コメントの gnibbler の功績によるものです)。streamgenerator()streamgenerator()

このアプローチは危険であることに注意してください。返されたジェネレーターを「消費」しないと、ストリームが小さくならないため、ますます多くなります。

于 2012-05-25T04:21:48.677 に答える
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]
于 2012-05-25T04:19:29.577 に答える
2

私が説明したことを実装しました:

リストが返される前、またはビルドされる前にリストを拒否することが必要な場合は、可能になる関数にフィルター引数を提供します。このフィルターがリストのプレフィックスを拒否すると、関数は現在の出力リストを破棄し、次のグループが開始されるまで出力リストへの追加をスキップします。

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]]
于 2012-05-25T04:33:48.737 に答える