开发者

Alternating between iterators in Python [duplicate]

开发者 https://www.devze.com 2022-12-15 20:54 出处:网络
This question already has answers here: 开发者_运维技巧 How do I merge two python iterators? (13 answers)
This question already has answers here: 开发者_运维技巧 How do I merge two python iterators? (13 answers) Closed 6 years ago.

What is the most efficient way to alternate taking values from different iterators in Python, so that, for example, alternate(xrange(1, 7, 2), xrange(2, 8, 2)) would yield 1, 2, 3, 4, 5, 6. I know one way to implement it would be:

def alternate(*iters):
    while True:
        for i in iters:
            try:
                yield i.next()
            except StopIteration:
                pass

But is there a more efficient or cleaner way? (Or, better yet, an itertools function I missed?)


For a "clean" implementation, you want

itertools.chain(*itertools.izip(*iters))

but maybe you want

itertools.chain(*itertools.izip_longest(*iters))


what about zip? you may also try izip from itertools

>>> zip(xrange(1, 7, 2),xrange(2, 8 , 2))
[(1, 2), (3, 4), (5, 6)]

if this is not what you want, please give more examples in your question post.


See roundrobin in the itertools "Recipes" section. It's a more general version of alternate.

def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
    # Recipe credited to George Sakkis
    pending = len(iterables)
    nexts = cycle(iter(it).__next__ for it in iterables)
    while pending:
        try:
            for next in nexts:
                yield next()
        except StopIteration:
            pending -= 1
            nexts = cycle(islice(nexts, pending))


You could define alternate like this:

import itertools
def alternate(*iters):   
    for elt in itertools.chain.from_iterable(
        itertools.izip(*iters)):
        yield elt

print list(alternate(xrange(1, 7, 2), xrange(2, 8, 2)))

This leaves open the question of what to do if one iterator stops before another. If you'd like to continue until the longest iterator is exhausted, then you could use itertools.izip_longest in place of itertools.izip.

import itertools
def alternate(*iters):   
    for elt in itertools.chain.from_iterable(
        itertools.izip_longest(*iters)):
        yield elt
print list(alternate(xrange(1, 7, 2), xrange(2, 10, 2)))

This will put yield

[1, 2, 3, 4, 5, 6, None, 8]

Note None is yielded when the iterator xrange(1,7,2) raises StopIteration (has no more elements).

If you'd like to just skip the iterator instead of yielding None, you could do this:

Dummy=object()

def alternate(*iters):   
    for elt in itertools.chain.from_iterable(
        itertools.izip_longest(*iters,fillvalue=Dummy)):
        if elt is not Dummy:
            yield elt


There are two problems with your attempt:

  1. You don't wrap each object in iters with iter() so it will fail with iterables such as list; and
  2. By passing on StopIteration your generator is an infinite loop.

Some simple code that does solves both those issues and is still easy to read and understand:

def alternate(*iters):
    iters = [iter(i) for i in iters]
    while True:
        for i in iters:
            yield next(i)

>>> list(alternate(range(1, 7, 2), range(2, 8, 2)))
[1, 2, 3, 4, 5, 6]


If they're the same length, itertools.izip can be leveraged like so:

def alternate(*iters):
    for row in itertools.izip(*iters):
       for i in row:
           yield i
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号