Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stream of cartesian product of other streams, each element as a List?

How can I implement a function using Java 8 to take some number of streams, and produce a stream in which each element is a list consisting of one member of the Cartesian product of the streams?

I've looked at this question -- that question uses an aggregator that is a BinaryOperator (taking two items of like type and producing an item of the same type). I'd like the items in the end result to be Lists rather than the types of the elements in the input streams.

Concretely, supposing my desired function is called product, the following:

Stream<List<String>> result =
    product(
        Stream.of("A", "B", "C", "D"),
        Stream.of("I", "J", "K"),
        Stream.of("Y", "Z")
    );

result.forEach(System.out::println);

should print:

[A, I, Y]
[A, I, Z]
[A, J, Y]
[A, J, Z]
[A, K, Y]
[A, K, Z]
[B, I, Y]
...
[D, K, Y]
[D, K, Z]

Ideally, I'd like for this operation to be as lazy as possible. For example, if the input streams are produced by Stream.generate(), it'd be great if the suppliers of those streams weren't executed until absolutely needed.

like image 790
pholser Avatar asked Jan 08 '17 01:01

pholser


1 Answers

A possible solution is as follows:

private static <T> Stream<List<T>> product(Stream<T>... streams) {
    if (streams.length == 0) {
        return Stream.empty();
    }
    List<List<T>> cartesian = streams[streams.length - 1]
            .map(x -> Collections.singletonList(x))
            .collect(Collectors.toList());
    for (int i = streams.length - 2; i >= 0; i--) {
        final List<List<T>> previous = cartesian;
        cartesian = streams[i].flatMap(x -> previous.stream().map(p -> {
            final List<T> list = new ArrayList<T>(p.size() + 1);
            list.add(x);
            list.addAll(p);
            return list;
        })).collect(Collectors.toList());
    }
    return cartesian.stream();
}

public static void main(String... args) {
    final Stream<List<String>> result =
            product(
                    Stream.of("A", "B", "C", "D"),
                    Stream.of("I", "J", "K"),
                    Stream.of("Y", "Z")
            );

    result.forEach(System.out::println);
}

The product-call returns a Stream<List<String>> result which prints as

[A, I, Y]
[A, I, Z]
[A, J, Y]
[A, J, Z]
[A, K, Y]
[A, K, Z]
[B, I, Y]
[B, I, Z]
[B, J, Y]
[B, J, Z]
[B, K, Y]
[B, K, Z]
[C, I, Y]
[C, I, Z]
[C, J, Y]
[C, J, Z]
[C, K, Y]
[C, K, Z]
[D, I, Y]
[D, I, Z]
[D, J, Y]
[D, J, Z]
[D, K, Y]
[D, K, Z]
like image 188
marco Avatar answered Nov 01 '22 18:11

marco