Do you know some neat Java libaries that allow you to make cartesian product of two (or more) sets?
For example: I have three sets. One with objects of class Person
Here is an Iterator
that gives the cartesian product of a two-dimensional array, where the arrays components represent the sets from the question (one can always convert actual Set
s to arrays):
public class CartesianIterator implements Iterator {
private final T[][] sets;
private final IntFunction arrayConstructor;
private int count = 0;
private T[] next = null;
public CartesianIterator(T[][] sets, IntFunction arrayConstructor) {
Objects.requireNonNull(sets);
Objects.requireNonNull(arrayConstructor);
this.sets = copySets(sets);
this.arrayConstructor = arrayConstructor;
}
private static T[][] copySets(T[][] sets) {
// If any of the arrays are empty, then the entire iterator is empty.
// This prevents division by zero in `hasNext`.
for (T[] set : sets) {
if (set.length == 0) {
return Arrays.copyOf(sets, 0);
}
}
return sets.clone();
}
@Override
public boolean hasNext() {
if (next != null) {
return true;
}
int tmp = count;
T[] value = arrayConstructor.apply(sets.length);
for (int i = 0; i < value.length; i++) {
T[] set = sets[i];
int radix = set.length;
int index = tmp % radix;
value[i] = set[index];
tmp /= radix;
}
if (tmp != 0) {
// Overflow.
return false;
}
next = value;
count++;
return true;
}
@Override
public T[] next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
T[] tmp = next;
next = null;
return tmp;
}
}
The basic idea is to treat count
as a multi-radix number (digit i
has its own radix which equals the length of the i
'th "set"). Whenever we have to resolve next
(that is, when hasNext()
is called and next
is null
), we decompose the number into its digits in this multi-radix. These digits are now used as the indices from which we draw elements from the different sets.
Example use:
String[] a = { "a", "b", "c"};
String[] b = { "X" };
String[] c = { "r", "s" };
String[][] abc = { a, b, c };
Iterable it = () -> new CartesianIterator<>(abc, String[]::new);
for (String[] s : it) {
System.out.println(Arrays.toString(s));
}
Output:
[a, X, r]
[b, X, r]
[c, X, r]
[a, X, s]
[b, X, s]
[c, X, s]
If one doesn't like arrays, the code is trivially convertible into using collections.
I guess this is more or less similar to the answer given by "user unknown", only without recursion and collections.