python izip 循环遍历所有可迭代对象,直到最长的结束

2 投票
3 回答
908 浏览
提问于 2025-04-16 06:44

这对我来说并不是一件简单的事情,我找不到任何现成的解决方案,所以也许你能给我指个方向,或者你有一个现成的、合适的、经过调试的解决方案?这里的“合适”是指它也能适用于那些不知道自己长度的迭代器(没有 __len__ 方法的迭代器),以及那些会耗尽的迭代器(比如链式迭代器);而“经过调试”则是指它运行得很快。

注意:由于需要缓存迭代器的输出以便重新迭代,所以不能使用就地解决方案(Glenn Maynard 提到过这一点)。

示例用法:

>>> list(izip_cycle(range(2), range(5), range(3)))
[(0, 0, 0), (1, 1, 1), (0, 2, 2), (1, 3, 0), (0, 4, 1)]
>>> from itertools import islice, cycle, chain
>>> list(islice(izip_cycle(cycle(range(1)), chain(range(1), range(2))), 6))
[(0, 0), (0, 0), (0, 1), (0, 0), (0, 0), (0, 1)]

3 个回答

0

在编程中,有时候我们需要让程序在特定的条件下执行某些操作。这就像给程序设定了一些规则,只有当这些规则被满足时,程序才会做出反应。

比如说,你可能想要在用户输入一个数字时,程序才进行计算。如果用户输入的不是数字,程序就应该给出提示,告诉用户输入不正确。这种情况下,我们就需要使用条件判断来检查用户的输入。

条件判断就像是在问一个问题:如果这个条件成立,那么就执行某个操作;如果不成立,就执行另一个操作。这样可以让程序更加智能,能够根据不同的情况做出不同的反应。

总之,条件判断是编程中非常重要的一部分,它帮助我们控制程序的行为,让程序能够根据实际情况做出适当的反应。

def izip_cycle_inplace(*iterables):
    def wrap(it):
        empty = True
        for x in it: empty = yield x
        if empty: return
        next(counter)
        while True:
            empty = True
            for x in it: empty = yield x
            if empty: raise ValueError('cannot cycle iterator in-place')
    iterators = [wrap(i) for i in iterables]
    counter = iter(iterators)
    next(counter)
    while True:
        yield [next(i) for i in iterators]

def izip_cycle(*iterables):
    def wrap(it):
        elements = []
        for x in it:
            yield x
            elements.append(x)
        if not elements: return
        next(counter)
        while True:
            for x in elements: yield x
    iterators = [wrap(i) for i in iterables]
    counter = iter(iterators)
    next(counter)
    while True:
        yield [next(i) for i in iterators]
1

这里有一个简单的方法,可能适合你的需求:

import itertools

def izip_cycle(*colls):
    maxlen = max(len(c) if hasattr(c,'__len__') else 0 for c in colls)
    g = itertools.izip(*[itertools.cycle(c) for c in colls])

    for _ in range(maxlen):
        yield g.next()

这个方法的第一步是找出最长序列的长度,这样它就知道要重复多少次。如果序列没有 __len__ 方法,它会被认为长度为0;这可能是你想要的——如果你有一个无限的序列,你可能只想在有限的序列上重复。不过,这个方法不适用于没有长度的有限迭代器。

我们通常会使用 itertools.cycle 来创建每个迭代器的循环版本,然后用 itertools.zip 把它们组合在一起。

最后,我们会从组合中逐个输出结果,直到达到我们想要的结果数量。

如果你想让这个方法适用于没有 len 的有限迭代器,我们需要自己做更多的工作:

def izip_cycle(*colls):
    iters = [iter(c) for c in colls]
    count = len(colls)
    saved = [[] for i in range(count)]
    exhausted = [False] * count

    while True:
        r = []

        for i in range(count):
            if not exhausted[i]:
                try:
                    n = iters[i].next()
                    saved[i].append(n)
                    r.append(n)
                except StopIteration:
                    exhausted[i] = True
                    if all(exhausted):
                        return
                    saved[i] = itertools.cycle(saved[i])
            if exhausted[i]:
                r.append(saved[i].next())

        yield r

这基本上是对 Python文档中 itertools.cycle 的实现 的扩展,可以处理多个序列。我们把已经看到的项目保存在 saved 中以便重复,并在 exhausted 中跟踪哪些序列已经用完。

因为这个版本会等到所有序列都用完,所以如果你传入一个无限的序列,循环就会一直进行下去。

1

这里有一些灵感来自于 itertools.teeitertools.cycle。它适用于任何类型的可迭代对象:

class izip_cycle(object):
    def __init__(self, *iterables ):
        self.remains = len(iterables)
        self.items = izip(*[self._gen(it) for it in iterables])

    def __iter__(self):
        return self.items

    def _gen(self, src):
        q = []
        for item in src:
            yield item
            q.append(item)

        # done with this src
        self.remains -=1
        # if there are any other sources then cycle this one
        # the last souce remaining stops here and thus stops the izip
        if self.remains:
            while True:
                for item in q:
                    yield item

撰写回答