python izip循环遍历所有迭代,直到最长完成

时间:2010-11-08 16:32:12

标签: python iterator

这对我来说并不是一项微不足道的任务,我找不到任何收据,所以也许你可以指点我或者你有一个现成的,适当的,经过精心调整的解决方案吗?正确的含义也适用于不知道自己的长度(没有__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 :(得分: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将它们压缩在一起。

最后,我们从我们的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 implementation of itertools.cycle in the documentation的扩展,可以在多个序列上运行。我们查看了我们在saved中看到的项目,以便重复并跟踪exhausted中已经用完的序列。

由于此版本等待所有序列耗尽,如果你传递了无限的东西,循环将永远运行。

答案 1 :(得分: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

答案 2 :(得分: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]