5

我有一系列生成器: (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]

不幸的是,这些项目的长度不一定相同,而且 zip 技巧似乎对生成器不起作用。

到目前为止,我的代码非常丑陋,我找不到任何接近干净解决方案的东西。帮助?

4

4 回答 4

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]]
>>> 
于 2013-01-17T11:21:34.917 回答
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]
于 2013-01-17T12:02:02.303 回答
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]
于 2013-01-17T11:54:36.300 回答
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
于 2013-01-17T11:46:05.207 回答