Alternating between iterators in Python [duplicate]
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:
- You don't wrap each object in
iters
withiter()
so it will fail with iterables such aslist
; and - By
pass
ing onStopIteration
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
精彩评论