Ответ 1
Нет точного решения itertools
, но достаточно простой комбинации функций itertools
:
def chain_imap_accumulate(seq, f):
def acc_f(x):
for n in f(x[-1]):
yield x + (n,)
return chain.from_iterable(imap(acc_f, seq))
def accumulative_product(*generators):
head, tail = generators[0], generators[1:]
head = imap(tuple, head())
return reduce(chain_imap_accumulate, tail, head)
Быстрый тест. Определения:
from itertools import chain, imap, izip
chain_ = chain.from_iterable
def A():
yield 'A'
yield 'B'
def B(x):
yield int(x, 16)
yield int(x, 16) + 1
def C(x):
yield str(x) + 'Z'
yield str(x) + 'Y'
И результат:
>>> list(accumulative_product(A, B, C))
[('A', 10, '10Z'), ('A', 10, '10Y'),
('A', 11, '11Z'), ('A', 11, '11Y'),
('B', 11, '11Z'), ('B', 11, '11Y'),
('B', 12, '12Z'), ('B', 12, '12Y')]
Почти вся сложность возникает из-за накопления входов, как показывает быстрый "вывод" вышеприведенного кода. Окончательные значения (c
) могут быть сгенерированы с использованием всего лишь нескольких вложенных конструкций itertools
:
>>> list(chain_(imap(C, chain_(imap(B, (A()))))))
['10Z', '10Y', '11Z', '11Y', '11Z', '11Y', '12Z', '12Y']
Это можно обобщить с помощью reduce
. Для работы с reduce
, chain_imap
не может использовать стандартный порядок аргументов imap
. Он должен быть заменен:
def chain_imap(seq, f):
return chain.from_iterable(imap(f, seq))
Это дает те же результаты:
>>> list(reduce(chain_imap, [B, C], A()))
['10Z', '10Y', '11Z', '11Y', '11Z', '11Y', '12Z', '12Y']
Последняя задача заключается в накоплении начальных значений, так что у вас есть доступ к a
, b
и c
. Для этого нужно немного подумать, но реализация довольно проста - нам просто нужно преобразовать f
в функцию, которая игнорирует все входные значения, но последние, и добавляет новые значения к полному вводу:
def chain_imap_accumulate(seq, f):
def acc_f(x):
for n in f(x[-1]):
yield x + (n,)
return chain.from_iterable(imap(acc_f, seq))
Это требует, чтобы первые входы были обернуты в кортежи, поэтому мы сопоставляем a
с tuple
:
>>> list(reduce(chain_imap_accumulate, [B, C], imap(tuple, A())))
[('A', 10, '10Z'), ('A', 10, '10Y'),
('A', 11, '11Z'), ('A', 11, '11Y'),
('B', 11, '11Z'), ('B', 11, '11Y'),
('B', 12, '12Z'), ('B', 12, '12Y')]
Измените приведенное выше для ясности, и получится код в верхней части этого ответа.
Кстати, chain_imap_accumulate
можно переписать немного более коротко, используя ген. Это можно комбинировать с более короткой версией accumulative_product
для очень компактного определения (если вас интересует такая вещь). Это также происходит, чтобы полностью исключить зависимость itertools:
def chain_map_accumulate(seq, f):
return (x + (n,) for x in seq for n in f(x[-1]))
def accumulative_product2(*gens):
return reduce(chain_map_accumulate, gens[1:], (tuple(x) for x in gens[0]()))