Заменить вложенный цикл на Java 8 flatmap
Я пытаюсь использовать flatmap для создания вложенного цикла с помощью Stream API, но я не могу понять это. В качестве примера я хочу воссоздать следующий цикл:
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);
Я могу сделать это так, но это выглядит ужасно
System.out.println("*** Nested Stream ***");
xs.stream().forEach(x ->
ys.stream().forEach(y -> System.out.println(x + " + " + y))
);
Flatmap выглядит многообещающе, но как я могу получить доступ к переменной во внешнем цикле?
System.out.println("*** Flatmap *** ");
xs.stream().flatMap(x -> ys.stream()).forEach(y -> System.out.println("? + " + y));
Выход:
*** 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
3 ответа
Вы должны создать нужные элементы в flatMap
этап, как:
xs.stream().flatMap(x -> ys.stream().map(y -> x + " + " + y)).forEach(System.out::println);
Как правило, нет необходимости 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
здесь нет необходимости в Stream API.
Да, выглядит красиво, но только с такими детскими заданиями. Вы создаете / закрываете новый поток для каждого элемента только для объединения их в результирующий поток. И все это только для распечатки?
В отличие от forEach
обеспечивает однострочное решение без каких-либо затрат производительности (стандарт foreach
внутри).
По сути, это декартово произведение для этих списков. Я бы сначала объединил их в один список:
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);
Затем создайте поток списков, и каждый из списков будет сопоставлен с его собственным потоком и сохранит этот материал в Supplier
:
Supplier<Stream<String>> result = 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)))
Reduce возвращает необязательное значение, поэтому для обработки отсутствующего значения я верну пустой поток строки:
.orElse(() -> Stream.of(""));
В конце концов, нам просто нужно получить значение поставщика (который будет потоком строк) и распечатать его:
s.get().forEach(System.out::println);
Весь метод будет выглядеть так:
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);
}