When working with multiple data sources or sequences, we sometimes need to interleave their elements — for example, by combining readings from several sensors in time order, distributing tasks evenly among workers, or blending separate data lists for a report or visualization.
An iterator that interleaves elements lets you handle round-robin–style processing naturally and efficiently, without building large intermediate structures or writing complex loops.
To make this more concrete, imagine a few simple scenarios:
- You have three sensors — measuring temperature, pressure, and humidity — each producing a sequence of readings. Interleaving their data allows you to process the values in real time, one from each source at a time.
- You’re preparing a performance report and want to weave together data from several machines so that each contributes its next measurement in turn.
- You want to distribute tasks evenly among several workers or devices — cycling through them one by one, assigning a new job each time.
In all these cases, an interleaving iterator provides a clean and Pythonic solution.
In this post, you’ll see how to build an iterator that interleaves elements from multiple iterable objects — that is, it yields one element from each in turn, cycling through them until one of them runs out.
Suppose we have three strings: ‘ABC’, ‘DEF’, and ‘GHI’. The iterator should then produce the sequence:
‘A’, ‘D’, ‘G’, ‘B’, ‘E’, ‘H’, ‘C’, ‘F’, ‘I’.
There are several ways to implement this behavior. The key idea in each case is the same:
- First, create iterators from the input iterables using the built-in iter() function.
- Store those iterators in the same order as the input sequences, so we can rotate through them in turn.
- Then build the logic that repeatedly retrieves the next element from each iterator, cycling until one of them is exhausted.
The implementations differ mainly in how this cyclic retrieval is handled.
Below you can find a few possible implementations, each with explanatory comments.
The first defines a dedicated iterator class, the next two are generator functions, and the last one returns an iterator directly.
As you can see, the last version is by far the most compact.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
from itertools import count class ElementInterleavingIterator: """An iterator that yields elements from the given iterables alternately, one after another. Example: 'ABC', 'DEF' -> 'A', 'D', 'B', 'E', 'C', 'F' """ def __init__(self, *iterable_objects): # Create and store iterators for each iterable object. self.iterators = [iter(x) for x in iterable_objects] # Store the number of iterables. self.n = len(iterable_objects) # Create a counter that generates increasing integers. self.counter = count() def __next__(self): # Generate cyclic indices using modulo arithmetic. i = next(self.counter) % self.n # Retrieve the next element from the current iterator. return next(self.iterators[i]) def __iter__(self): return self # TEST print(''.join(ElementInterleavingIterator('ph', 'yo', 'tn'))) # Output: python |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
from itertools import count, cycle def interleaver_gen1(*iterable_objects): iterators = [iter(x) for x in iterable_objects] n = len(iterable_objects) counter = count() while True: try: # Yield elements until one iterator is exhausted. yield next(iterators[next(counter) % n]) except StopIteration: break def interleaver_gen2(*iterable_objects): iterators = [iter(x) for x in iterable_objects] while True: for itr in iterators: try: yield next(itr) # Stop completely when any iterator is exhausted. except StopIteration: return def interleaver_fn(*iterable_objects): # Create an iterator for each input iterable, # then repeatedly call next() on them in a cyclic sequence. return map(next, cycle(map(iter, iterable_objects))) # TESTS print(''.join(interleaver_gen1('iro', 'tar', 'et'))) # Output: iterator print(''.join(interleaver_gen2('get', 'ero', 'nar'))) # Output: generator print(''.join(interleaver_fn('itsd', 'to u', 'eoml', 'rloe'))) # Output: itertools modul |
Most of these implementations use the
itertools module from the Python standard library.
In fact, as a general rule, whenever your task involves creating or manipulating iterators, it’s worth exploring what
itertools has to offer. It can greatly simplify your code and make it both more readable and more expressive.
You can find a more detailed discussion of the available iterators in the itertools module, with plenty of examples, in the chapter “Special Iterators” of the e-book Python Knowledge Building Step by Step.