#java #java-stream
Вопрос:
Я пытаюсь создавать различные комбинации из массивов нескольких размеров, выбирая по одному элементу из каждого. проверьте код ниже, переменная collect выдает правильный вывод, но collect1 выдает ошибку.
int[] pants= {3, 5, 7}, shirts = {4, 7, 8},
skirts = {5, 8}, shoes = {3};
List<int[]> collect = Arrays.stream(pants)
.mapToObj(i -> Arrays.stream(shirts)
.mapToObj(j -> new int[] {i,j}))
.flatMap(Function.identity())
.collect(Collectors.toList());
List<int[]> collect1 = Arrays.stream(pants)
.mapToObj(i -> Arrays.stream(shirts)
.mapToObj(j -> Arrays.stream(skirts)
.mapToObj(k -> Arrays.stream(shoes)
.mapToObj(l -> new int[] {i,j,k,l}))))
.flatMap(Function.identity())
.collect(Collectors.toList());
collect.forEach(ar->System.out.println(Arrays.toString(ar)));
ошибка для collect1: Несоответствие типов: не удается преобразовать из списка<Поток<Поток<int[]><int[]>><int []>> в список<int[]>>><int[]>
Ответ №1:
Часто бывает хорошей идеей свести проблему к минимуму, насколько это возможно. Вот 2 списка с 2 пунктами. Объединить их означает взять первый элемент первого списка, а затем создать комбинацию с каждым из каждого второго списка. Затем возьмите второй пункт первого списка и объедините его с каждым пунктом из второго списка. И так далее …
Псевдокод:
combine({1, 2}, {3, 4}) == { { 1, 3 }, { 1, 4 }, { 2, 3 }, { 2, 4 } };
Теперь давайте попробуем выразить это потоками:
int[] first = { 1, 2 }, second = { 3, 4 };
int[][] combinations = Arrays.stream(first)
.flatMap(a -> Arrays.stream(second)
.map(b -> new int[]{a, b}))
.toArray();
Самое сложное в том, что это не работает! Потоки (и обобщения в целом) в Java могут использовать только ссылочные типы, они не поддерживают простые типы значений, такие как int
. Это немного усложняет код. Тип int[]
расширяется Object
, поэтому мы можем преобразовать код следующим образом:
Object[] combinations = Arrays.stream(first)
.mapToObj(a -> Arrays.stream(second)
.mapToObj(b -> new int[]{a, b}))
.flatMap(x -> x)
.toArray();
Добавление третьего входного массива делает это:
Object[] combinations = Arrays.stream(first)
.mapToObj(a -> Arrays.stream(second)
.mapToObj(b -> Arrays.stream(third)
.mapToObj(c -> new int[]{a, b, c}))
.flatMap(x -> x))
.flatMap(x -> x)
.toArray();
или со списком массивов int:
List<int[]> combinations = Arrays.stream(first)
.mapToObj(a -> Arrays.stream(second)
.mapToObj(b -> Arrays.stream(third)
.mapToObj(c -> new int[]{a, b, c}))
.flatMap(x -> x))
.flatMap(x -> x)
.collect(Collectors.toList());
Промойте и повторяйте, пока не добавите все входные данные:
List<int[]> combinations = Arrays.stream(first)
.mapToObj(a -> Arrays.stream(second)
.mapToObj(b -> Arrays.stream(third)
.mapToObj(c -> Arrays.stream(fourth).mapToObj(d -> new int[]{a, b, c, d}))
.flatMap(x -> x))
.flatMap(x -> x))
.flatMap(x -> x)
.collect(Collectors.toList());
Если бы вы начали с целых чисел в коробках, ваш поток стал бы намного проще:
Integer[] first = { 1, 2 }, second = { 3, 4 }, third = { 5, 6 }, fourth = { 7, 8 };
List<int[]> combinations = Arrays.stream(first)
.flatMap(a -> Arrays.stream(second)
.flatMap(b -> Arrays.stream(third)
.flatMap(c -> Arrays.stream(fourth)
.map(d -> new int[]{a, b, c, d}))))
.collect(Collectors.toList());
Аналогичного эффекта можно достичь, преобразовав каждый IntStream
из них в Stream<Integer>
первый. Я нахожу, что это легче читать и понимать, чем стиль mapToObj.flatMap:
List<int[]> combinations = Arrays.stream(first)
.boxed()
.flatMap(a -> Arrays.stream(second)
.boxed()
.flatMap(b -> Arrays.stream(third)
.boxed()
.flatMap(c -> Arrays.stream(fourth)
.boxed()
.map(d -> new int[]{a, b, c, d}))))
.collect(Collectors.toList());
Помните о влиянии на производительность при создании большого количества вложенных и небольших потоков, а также при упаковке/распаковке примитивных значений.
Ответ №2:
Согласно документам для MapToObj
: «Возвращает поток, возвращающий объектное значение, состоящий из результатов применения данной функции к элементам этого потока».
Поэтому каждый раз, когда вы звоните ему, он возвращает Stream
«а». Таким образом, если вам нужны только значения в потоке, вы должны добавить вызов flatmap
для каждого вызова в MapToObj
:
List<int[]> collect1 = Arrays.stream(pants)
.mapToObj(i -> Arrays.stream(shirts)
.mapToObj(j -> Arrays.stream(skirts)
.mapToObj(k -> Arrays.stream(shoes)
.mapToObj(l -> new int[] {i,j,k,l}))))
.flatMap(Function.identity())
.flatMap(Function.identity())
.flatMap(Function.identity())
.collect(Collectors.toList());