Подтвердить что ты не робот

Может ли производить несколько последовательных генераторов?

Вот две функции, которые разделяют итерируемые элементы на под-списки. Я считаю, что этот тип задачи запрограммирован много раз. Я использую их для разбора файлов журналов, состоящих из repr строк типа ( "результат", "случай", 123, 4.56) и ( "дамп",..) и т.д.

Я хотел бы изменить их так, чтобы они приводили итераторы, а не списки. Поскольку список может расти довольно крупным, но я могу решить принять его или пропустить его на основе первых нескольких элементов. Кроме того, если доступна версия 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

edit: мой собственный ответ

Спасибо всем, я могу написать то, что я просил! Конечно, что касается функции "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')
4b9b3361

Ответ 1

Адам ответ хороший. это на всякий случай вам любопытно, как это сделать вручную:

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, nonlocal, потому что я назначен stream внутри generator(), который сделал (вторая переменная, также называемая) stream local по generator() по умолчанию - кредит gnibbler в комментариях).

обратите внимание, что этот подход опасен - если вы не "потребляете" генераторы, которые возвращаются, вы получите все больше и больше, потому что поток не становится меньше.

Ответ 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]

Ответ 3

Я реализовал то, что я описал:

Если вы хотите отклонить список до его возврата или даже построить, предоставив аргумент фильтра функциям, которые будут возможное. Когда этот фильтр отклоняет префикс списка, функция выкинуть текущий список вывода и пропустить добавление к списку результатов пока не будет запущена следующая группа.

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