我有一系列生成器:(gen_0,gen_1,... gen_n)
这些生成器将懒洋洋地创建它们的值但是有限且可能具有不同的长度。
我需要能够构建另一个生成器,按顺序生成每个生成器的第一个元素,然后是第二个生成器,依此类推,从已经耗尽的生成器中跳过值。
我认为这个问题类似于取元组
((1, 4, 7, 10, 13, 16), (2, 5, 8, 11, 14), (3, 6, 9, 12, 15, 17, 18))
并遍历它,以便按顺序产生从1到18的数字。
我正在使用(genA,genB,genC)解决这个简单的例子,genA产生的值来自(1,4,7,10,13,16),genB产生(2,5,8, 11,14)和genC屈服(3,6,9,12,15,17,18)。
为了解决元组元组的更简单问题,答案是相当简单的 元组的元素长度相同。如果变量' a'提到元组,你可以用
[i for t in zip(*a) for i in t]
不幸的是,这些物品的长度不一定相同,拉链技巧似乎也不适用于发电机。
到目前为止,我的代码非常难看,而且我没有找到任何接近干净解决方案的东西。帮助
答案 0 :(得分:8)
我认为你需要itertools.izip_longest
>>> list([e for e in t if e is not None] for t in itertools.izip_longest(*some_gen,
fillvalue=None))
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15], [16, 17], [18]]
>>>
答案 1 :(得分:4)
如果查看itertools.izip_longest
的文档,您会发现它提供了纯Python实现。修改此实现很容易,因此它会产生您需要的结果(就像izip_longest
一样,但没有任何fillvalue
):
class ZipExhausted(Exception):
pass
def izip_longest_nofill(*args):
"""
Return a generator whose .next() method returns a tuple where the
i-th element comes from the i-th iterable argument that has not
yet been exhausted. The .next() method continues until all
iterables in the argument sequence have been exhausted and then it
raises StopIteration.
>>> list(izip_longest_nofill(*[xrange(i,2*i) for i in 2,3,5]))
[(2, 3, 5), (3, 4, 6), (5, 7), (8,), (9,)]
"""
iterators = map(iter, args)
def zip_next():
i = 0
while i < len(iterators):
try:
yield next(iterators[i])
i += 1
except StopIteration:
del iterators[i]
if i == 0:
raise ZipExhausted
try:
while iterators:
yield tuple(zip_next())
except ZipExhausted:
pass
这样就无需重新过滤izip_longest
的输出以丢弃填充值。或者,如果您想要“扁平”输出:
def iter_round_robin(*args):
"""
Return a generator whose .next() method cycles round the iterable
arguments in turn (ignoring ones that have been exhausted). The
.next() method continues until all iterables in the argument
sequence have been exhausted and then it raises StopIteration.
>>> list(iter_round_robin(*[xrange(i) for i in 2,3,5]))
[0, 0, 0, 1, 1, 1, 2, 2, 3, 4]
"""
iterators = map(iter, args)
while iterators:
i = 0
while i < len(iterators):
try:
yield next(iterators[i])
i += 1
except StopIteration:
del iterators[i]
答案 2 :(得分:2)
另一个itertools选项,如果您希望它们全部折叠在一个列表中;这个(正如@ gg.kaspersky已经在另一个线程中指出的那样)不处理生成的None
值。
g = (generator1, generator2, generator3)
res = [e for e in itertools.chain(*itertools.izip_longest(*g)) if e is not None]
print res
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]
答案 3 :(得分:1)
您可能会考虑itertools.izip_longest
,但如果None是有效值,则该解决方案将失败。这是一个示例“另一个生成器”,它完全按照您的要求执行,并且非常干净:
def my_gen(generators):
while True:
rez = ()
for gen in generators:
try:
rez = rez + (gen.next(),)
except StopIteration:
pass
if rez:
yield rez
else:
break
print [x for x in my_gen((iter(xrange(2)), iter(xrange(3)), iter(xrange(1))))]
[(0, 0, 0), (1, 1), (2,)] #output