Iteratively compute the Cartesian product of an arbitrary number of sets

Viewed 13232

I want to compute the cartesian product of an arbitrary number of nonempty sets in Java.

I've wrote that iterative code...

public static <T> List<Set<T>> cartesianProduct(List<Set<T>> list) {
    List<Iterator<T>> iterators = new ArrayList<Iterator<T>>(list.size());
    List<T> elements = new ArrayList<T>(list.size());
    List<Set<T>> toRet = new ArrayList<Set<T>>();
    for (int i = 0; i < list.size(); i++) {
        iterators.add(list.get(i).iterator());
        elements.add(iterators.get(i).next());
    }
    for (int j = 1; j >= 0;) {
        toRet.add(Sets.newHashSet(elements));
        for (j = iterators.size()-1; j >= 0 && !iterators.get(j).hasNext(); j--) {
            iterators.set(j, list.get(j).iterator());
            elements.set(j, iterators.get(j).next());
        }
        elements.set(Math.abs(j), iterators.get(Math.abs(j)).next());
    }
    return toRet;
}

...but I found it rather inelegant. Someone has a better, still iterative solution? A solution that uses some wonderful functional-like approach? Otherwise... suggestion about how to improve it? Errors?

10 Answers

You can use Stream.reduce method.

Java 9 without additional libraries.

public static <U> List<Set<U>> cartesianProduct(List<Set<? extends U>> sets) {
    // incorrect incoming data
    if (sets == null) return Collections.emptyList();
    return sets.stream()
            // non-null and non-empty sets
            .filter(set -> set != null && set.size() > 0)
            // represent each set element as Set<U>
            .map(set -> set.stream().map(Set::<U>of)
                    // Stream<List<Set<U>>>
                    .collect(Collectors.toList()))
            // summation of pairs of inner sets
            .reduce((set1, set2) -> set1.stream()
                    // combinations of inner sets
                    .flatMap(inner1 -> set2.stream()
                            // merge two inner sets into one
                            .map(inner2 -> Stream.of(inner1, inner2)
                                    .flatMap(Set::stream)
                                    .collect(Collectors.toSet())))
                    // list of combinations
                    .collect(Collectors.toList()))
            // List<Set<U>>
            .orElse(Collections.emptyList());
}

public static void main(String[] args) {
    Set<Integer> set1 = Set.of(1, 2);
    Set<Double> set2 = Set.of(3.0, 4.0);
    Set<Long> set3 = Set.of(5L, 6L);

    List<Set<Number>> sets = cartesianProduct(List.of(set1, set2, set3));
    // output
    sets.forEach(System.out::println);
}

Output (the order of the elements may differ):

[1, 3.0, 5]
[1, 3.0, 6]
[1, 4.0, 5]
[1, 4.0, 6]
[2, 3.0, 5]
[2, 3.0, 6]
[2, 4.0, 5]
[2, 4.0, 6]

See also: Cartesian product of an arbitrary number of sets

Related