Python是否有一种内置函数可以交错生成器/序列?

16

我注意到itertools似乎没有一种能够交替从几个其他可迭代对象中获取元素的函数(与将它们压缩在一起不同):

def leaf(*args): return (it.next() for it in cycle(imap(chain,args)))
tuple(leaf(['Johann', 'Sebastian', 'Bach'], repeat(' '))) => ('Johann', ' ', 'Sebastian', ' ', 'Bach', ' ')

(编辑) 我提出这个问题的原因是我想避免不必要的压缩/展平发生。

显然,“叶子”的定义很简单,但如果有一个预定义的函数可以做同样的事情,我宁愿使用它,或者一个非常清晰的生成器表达式。 是否有这样一个内置函数,在itertools中,或在其他知名库中,或适当的习惯用语?

编辑2:甚至可以使用“functional”包进行更简洁的定义:

from itertools import *
from functional import *

compose_mult = partial(reduce, compose)
leaf = compose_mult((partial(imap, next), cycle, partial(imap, chain), lambda *args: args))

8
完全无关的一点,我忍不住要提一下,所谓的“Bach”是指约翰·塞巴斯蒂安·巴赫(Johann Sebastian Bach),而不是约翰(John). - 9000
@9000:很好 - 这是我在单元测试中不经意地写下的。 - Marcin
3个回答

16

你需要使用内置的zipitertools.chain.from_iterable来将结果展平:

>>> import itertools
>>> list(zip(['Johann', 'Sebastian', 'Bach'], itertools.repeat(' ')))
[('Johann', ' '), ('Sebastian', ' '), ('Bach', ' ')]
>>> list(itertools.chain.from_iterable(_))
['Johann', ' ', 'Sebastian', ' ', 'Bach', ' ']

请注意,我仅仅使用list是为了使输出更美观。使用标准的itertools时,leaf的替代实现如下:

leaf = lambda *a: itertools.chain.from_iterable(itertools.izip(*a)) # Python 2.x
leaf = lambda *a: itertools.chain.from_iterable(zip(*a))            # Python 3.x

@larsmans 以何种方式更安全?自Python 3.0起,itertools.izip已被移除。 - phihag
1
好的。我仍然主要生活在Python 2.x的世界中。 - Fred Foo
2
@Marcin,你对压缩和展平有什么问题? - phihag
3
代表@Marcin发言,假设输入的可迭代对象非常庞大或无限,并且他可能更愿意迭代地处理它们,而不是一次性处理完所有内容。 - Kirk Strauser
@KirkStrauser 嗯?zip(在Python 2中为itertools.izip)和chain.from_iterable都返回生成器,并且可以很好地处理巨大或无限的输入。 - phihag
显示剩余5条评论

8

如果涉及到编程,我会建议使用itertools roundrobin() recipe。但是在你的例子中,它会产生一个无限序列,因为它停止于最长的可迭代对象,而不是最短的。当然,这很容易修复。也许值得尝试一下以获得不同的方法?


那就是我所想的,只不过 (a) 我更喜欢叶子语义 (b) 我的定义更加简洁。 - Marcin

2
这个自定义函数交替迭代器,直到每个迭代器都被耗尽。
def interleave_iterators(*iterators):
    finished = [False for x in range(len(iterators))]
    stop_cond = functools.reduce(lambda x,y:not x or not y,finished)
    while stop_cond:
        for i,it in enumerate(iterators):
            try:
                yield next(it)
            except StopIteration:
                finished[i] = True
        stop_cond = functools.reduce(lambda x,y:not x or not y,finished)

例如。
it1 = iter([2,4,6,8])
it2 = iter([1,3,5,7,9,11,13])
for x in interleave_iterators(it1,it2): # prints 2 1 4 3 6 5 8 7 9 11 13
    print(str(x),end=" ")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接