Python izip, который перебирает все итерации, пока не закончится самый длинный - PullRequest
2 голосов
/ 08 ноября 2010

Это оказалось не тривиальной задачей для меня, и я не смог найти квитанцию, так что, может быть, вы можете указать мне одну или у вас есть готовое, правильное и хорошо настроенное решение для этого?Правильное значение работает также для итераторов, которые не знают собственной длины (без __len__) и работает для исчерпывающих итераторов (например, прикованных итераторов);хорошо настроенный, означающий быстро.

Примечание: решение на месте невозможно из-за необходимости кэшировать выходные данные итераторов, чтобы повторять их ( Гленн Мейнард указал на это).

Пример использования:

>>> 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 ]

1 голос
/ 09 ноября 2010

Вот что-то вдохновленное itertools.tee и itertools.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
1 голос
/ 08 ноября 2010

Простой подход, который может работать для вас, в зависимости от ваших требований:

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.

Поскольку эта версия ожидает завершения всех последовательностей, если вы передадите что-то бесконечное, цикл будет продолжаться вечно.

0 голосов
/ 08 ноября 2010
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]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...