@nb.njit(nb.int32[:,:](nb.int32[:]))
def cproduct_idx(sizes: np.ndarray):
"""Generates ids tuples for a cartesian product"""
assert len(sizes) >= 2
tuples_count = np.prod(sizes)
tuples = np.zeros((tuples_count, len(sizes)), dtype=np.int32)
tuple_idx = 0
# stores the current combination
current_tuple = np.zeros(len(sizes))
while tuple_idx < tuples_count:
tuples[tuple_idx] = current_tuple
current_tuple[0] += 1
# using a condition here instead of including this in the inner loop
# to gain a bit of speed: this is going to be tested each iteration,
# and starting a loop to have it end right away is a bit silly
if current_tuple[0] == sizes[0]:
# the reset to 0 and subsequent increment amount to carrying
# the number to the higher "power"
current_tuple[0] = 0
current_tuple[1] += 1
for i in range(1, len(sizes) - 1):
if current_tuple[i] == sizes[i]:
# same as before, but in a loop, since this is going
# to get run less often
current_tuple[i + 1] += 1
current_tuple[i] = 0
else:
break
tuple_idx += 1
return tuples
@nb.njit
def cartesian_product(*arrays):
sizes = [len(a) for a in arrays]
sizes = np.asarray(sizes, dtype=np.int32)
tuples_count = np.prod(sizes)
array_ids = cproduct_idx(sizes)
tuples = np.zeros((tuples_count, len(sizes)))
for i in range(len(arrays)):
tuples[:, i] = arrays[i][array_ids[:, i]]
return tuples
@nb.njit
def cartesian_product_repeat(array, repeat):
sizes = [len(array) for _ in range(repeat)]
sizes = np.asarray(sizes, dtype=np.int32)
tuples_count = np.prod(sizes)
array_ids = cproduct_idx(sizes)
tuples = np.zeros((tuples_count, len(sizes)))
for i in range(repeat):
tuples[:, i] = array[array_ids[:, i]]
return tuples