Может ли производить несколько последовательных генераторов?
Вот две функции, которые разделяют итерируемые элементы на под-списки. Я считаю, что этот тип задачи запрограммирован много раз. Я использую их для разбора файлов журналов, состоящих из 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')
Ответы
Ответ 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]]