7

I'm trying to use flatmap to make a nested loop with the Stream API, but I can't seem to figure it out. As an example, I want to recreate the following loop:

List<String> xs = Arrays.asList(new String[]{ "one","two", "three"});
List<String> ys = Arrays.asList(new String[]{"four", "five"});

System.out.println("*** Nested Loop ***");
for (String x : xs)
    for (String y : ys)
        System.out.println(x + " + " + y);

I can do it like this, but this seems ugly:

System.out.println("*** Nested Stream ***");
xs.stream().forEach(x ->
    ys.stream().forEach(y -> System.out.println(x + " + " + y))
);

Flatmap looks promising, but how can I access the variable in the outer loop?

System.out.println("*** Flatmap *** ");
xs.stream().flatMap(x -> ys.stream()).forEach(y -> System.out.println("? + " + y));

Output:

*** Nested Loop ***
one + four
one + five
two + four
two + five
three + four
three + five
*** Nested Stream ***
one + four
one + five
two + four
two + five
three + four
three + five
*** Flatmap *** 
? + four
? + five
? + four
? + five
? + four
? + five
Alexis C.
  • 91,686
  • 21
  • 171
  • 177
wvdz
  • 16,251
  • 4
  • 53
  • 90
  • 1
    Possible duplicate of [How to iterate nested for loops referring to parent elements?](http://stackoverflow.com/questions/29235567/how-to-iterate-nested-for-loops-referring-to-parent-elements) – px06 Nov 18 '16 at 14:01

3 Answers3

12

You have to create your desired elements in the flatMap stage, like:

xs.stream().flatMap(x -> ys.stream().map(y -> x + " + " + y)).forEach(System.out::println);
Flown
  • 11,480
  • 3
  • 45
  • 62
  • Thanks. It seems to me the nested Stream option would be easier to use though. In the given example you can quite easily move the combination operation to flatmap, but in a lot of other cases this will be more work, especially when adding more levels to the nesting. – wvdz Nov 18 '16 at 14:16
  • 2
    @wvdz It always depends on the use case and what is more readable to you. – Flown Nov 18 '16 at 14:57
2

Normally, there is no need of the flatMap:

xs.forEach(x -> ys.stream().map(y ->  x + " + " + y).forEach(System.out::println)); // X
xs.forEach(x -> ys.forEach(y -> System.out.println(x + " + " + y))); // V

as well as there is no need of Stream API here.

Yes, it looks beautiful, but only with such childish tasks. You create/close a new stream for each element only to merge them into the resulting stream. And all of that is just for printing out?

In contrast, the forEach provides a one-line solution without any performance costs (a standard foreach inside).

Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
0

Basically, this is a cartesian product for those lists. I would combine them first into one list:

List<String> xs = Arrays.asList(new String[]{ "one","two", "three"});
List<String> ys = Arrays.asList(new String[]{"four", "five"});
List<List<String>> input = Arrays.asList(xs, ys);

Then create a stream of lists and each of the lists will map to its own stream and hold this stuff into Supplier:

Supplier<Stream<String>> result = input.stream() // Stream<List<String>>
                .<Supplier<Stream<String>>>map(list -> list::stream) // Stream<Supplier<Stream<String>>>

Then reduce this stream of suppliers and produce a cartesian product for the streams of strings that are belong to suppliers like this:

.reduce((sup1, sup2) -> () -> sup1.get().flatMap(e1 -> sup2.get().map(e2 -> e1 + e2)))

Reduce returns optional, so to handle absent value I will return an empty string stream:

.orElse(() -> Stream.of(""));

After all we just need to get the supplier value (which will be a stream of strings) and print it out:

s.get().forEach(System.out::println);

The whole method will look like:

public static void printCartesianProduct(List<String>... lists) {
        List<List<String>> input = asList(lists);
        Supplier<Stream<String>> s = input.stream()
                // Stream<List<String>>
                .<Supplier<Stream<String>>>map(list -> list::stream)
                // Stream<Supplier<Stream<String>>>
                .reduce((sup1, sup2) -> () -> sup1.get()
                        .flatMap(e1 -> sup2.get().map(e2 -> e1 + e2)))
                .orElse(() -> Stream.of(""));

        s.get().forEach(System.out::println);
    }