Using the roundrobin
recipe that Karl mentioned (copied verbatim from the recipes, could also import it from more-itertools). I think this will be faster, since all the hard work is done in C code of various itertools.
from itertools import repeat, chain, cycle, islice
def roundrobin(*iterables):
"roundrobin('ABC', 'D', 'EF') --> A D E B F C"
# Recipe credited to George Sakkis
num_active = len(iterables)
nexts = cycle(iter(it).__next__ for it in iterables)
while num_active:
try:
for next in nexts:
yield next()
except StopIteration:
# Remove the iterator we just exhausted from the cycle.
num_active -= 1
nexts = cycle(islice(nexts, num_active))
def pairs(a, b):
aseen = []
bseen = []
def agen():
for aa in a:
aseen.append(aa)
yield zip(repeat(aa), bseen)
def bgen():
for bb in b:
bseen.append(bb)
yield zip(aseen, repeat(bb))
return chain.from_iterable(roundrobin(agen(), bgen()))
a = ['C', 'B', 'A']
b = [3, 2, 1]
print(*pairs(a, b))
Output (Try it online!):
('C', 3) ('B', 3) ('C', 2) ('B', 2) ('A', 3) ('A', 2) ('C', 1) ('B', 1) ('A', 1)
Benchmark with two iterables of 2000 elements each (Try it online!):
50 ms 50 ms 50 ms Kelly
241 ms 241 ms 242 ms Karl
Alternatively, if the two iterables can be iterated multiple times, we don't need to save what we've seen (Try it online!):
def pairs(a, b):
def agen():
for i, x in enumerate(a):
yield zip(repeat(x), islice(b, i))
def bgen():
for i, x in enumerate(b, 1):
yield zip(islice(a, i), repeat(x))
return chain.from_iterable(roundrobin(agen(), bgen()))
(Will add to the benchmark later... Should be a little slower than my first solution.)
An extreme map/itertools version of that (Try it online!):
def pairs(a, b):
return chain.from_iterable(roundrobin(
map(zip,
map(repeat, a),
map(islice, repeat(b), count())),
map(zip,
map(islice, repeat(a), count(1)),
map(repeat, b))
))