Switch between iterators in Python

What is the most efficient way of alternating values ​​of values ​​from different iterators in Python, so for example alternate(xrange(1, 7, 2), xrange(2, 8, 2)) will give 1, 2, 3, 4, 5, 6 I know that one of the ways to implement it is:

 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, even better, the itertools function that I missed?)

+9
python iterator
Jan 07
source share
6 answers

how about zip? you can 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 provide more examples in your question.

+6
Jan 07 '10 at 2:51
source share

For a clean implementation you want

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

but maybe you want

 itertools.chain(*itertools.izip_longest(*iters)) 
+15
Jan 07 '10 at 2:55
source share

See roundrobin in the itertools Recipes section . This is a more general version of the alternative.

 def roundrobin(*iterables): "roundrobin('ABC', 'D', 'EF') --> ADEBFC" # 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)) 
+6
Jan 07
source share

You can define alternate as follows:

 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 want to continue until the longest iterator has been exhausted, you can use itertools.izip_longest instead 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 cause damage.

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

Note None appears when the xrange (1,7,2) iterator calls StopIteration (has no more elements).

If you want to just skip the iterator instead of yielding None , you can 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 
+2
Jan 07 '10 at 2:54
source share

If they are the same length, itertools.izip can be used like this:

 def alternate(*iters): for row in itertools.izip(*iters): for i in row: yield i 
+1
Jan 07 '10 at 2:55
source share

There are two problems with your attempt:

  • You don't transfer every object to iters with iter() , so it will fail with iterables, like list ; and
  • In pass ing to StopIteration your generator is an endless loop.

Some simple code that solves both of these problems 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] 
+1
Mar 23 '16 at 2:42 on
source share



All Articles