The other solutions produce the correct result, but if you just want one element of the Cartesian product, iterating the generator returned by itertools.product
is not the most efficient solution. You can directly build the item that you need without having to go through all the elements with a function like this:
from collections.abc import Sequence
def product_item(idx, *seqs, repeat=None):
# Ensure inputs are actual sequences (list, tuple, str...)
seqs = [seq if isinstance(seq, Sequence) else list(seq) for seq in seqs]
# Repeat if needed
if repeat is not None:
seqs = seqs * repeat
# Compute how many items does it take to advance on each sequence
step = 1
for seq in seqs:
step *= len(seq)
# Build product item
item = [None] * len(seqs)
for i, seq in enumerate(seqs):
step //= len(seq)
seq_idx = idx // step
idx %= step
item[i] = seq[seq_idx]
return tuple(item)
print(product_item(10, 'abc', repeat=3))
# ('b', 'a', 'b')
The complexity for this solution is O(1). A quick comparison:
import itertools
# Solution with islice
product_item_islice = lambda idx, *seqs, repeat=None: next(
itertools.islice(itertools.product(*seqs, repeat=repeat), idx, None))
idx = 100_000_000
seqs = ['abcdefgh']
repeat = 10
print(product_item(idx, *seqs, repeat=repeat))
# ('a', 'f', 'h', 'f', 'd', 'g', 'a', 'e', 'a', 'a')
print(product_item_islice(idx, *seqs, repeat=repeat))
# ('a', 'f', 'h', 'f', 'd', 'g', 'a', 'e', 'a', 'a')
%timeit product_item(idx, *seqs, repeat=repeat)
# 3.7 µs ± 46.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit product_item_islice(idx, *seqs, repeat=repeat)
# 448 ms ± 7.55 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)