Java 8 потоков: обрабатывать все возможные пары элементов из списка
У меня есть Collection
элементов произвольного класса. Я хочу выполнить итерацию по коллекции и выполнить некоторую операцию, используя элемент и каждый другой элемент коллекции один за другим (исключая сам элемент). Пусть для простоты List<Integer>
:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
С петлями for
это будет:
for (Integer i : list) {
for (Integer j : list) {
if (!i.equals(j)) System.out.println(i * 2 + j); //just for example
}
}
Вопрос как это сделать с Stream API?
То, к чему я пришел:
list.stream().forEach(i ->
list.stream().forEach(j -> {
if (!i.equals(j)) System.out.println(i * 2 + j);
})
);
Это не выглядит лучше, чем вложенный цикл. Есть ли более элегантный способ?
Ответы
Ответ 1
Вы можете сделать это, используя операцию flatMap
:
list.stream()
.flatMap(i -> list.stream().filter(j -> !i.equals(j)).map(j -> i * 2 + j))
.forEach(System.out::println);
Этот код создает поток списка ввода. Он плоский сопоставляет каждый элемент списка потоком, созданным тем же списком, где текущий элемент был отфильтрован, и каждый элемент этого нового списка является результатом операции i * 2 + j
.
Затем все элементы печатаются на консоли.
Ответ 2
Вы можете избежать сравнения объектов путем сопоставления с значениями int
заранее, например.
list.stream().mapToInt(Integer::intValue)
.flatMap(i -> list.stream().filter(j -> j!=i).mapToInt(j -> i*2 + j))
.forEach(System.out::println);
Но на самом деле вы выполняете условные операции и проверки равенства для того, что является инвариантным. Поскольку вся операция зависит от исходного списка, не меняющего его содержимого между собой, вы можете просто генерировать индексы списка спаривания в первую очередь:
final int end=list.size();
IntStream.range(0, end).flatMap(i ->
IntStream.concat(IntStream.range(0, i), IntStream.range(i+1, end))
.map(j -> list.get(i) * 2 + list.get(j)))
.forEach(System.out::println);
Если вы не хотите вычислять числовое значение, но создаете объект для пар, он становится немного более сложным из-за того, что IntStream
не имеет операции flatMapToObj
, поэтому нам нужна комбинация mapToObj
и flatMap
(если мы не используем boxed
). Таким образом, построение двухстрочного массива выглядит следующим образом:
IntStream.range(0, end).mapToObj(i ->
IntStream.concat(IntStream.range(0, i), IntStream.range(i+1, end))
.mapToObj(j -> new int[]{ list.get(i), list.get(j)}))
.flatMap(Function.identity())
.forEach(a -> System.out.println(a[0]*2+a[1]));
Конечно, для списка, такого же простого, как [ 1, 2, 3, 4, 5 ]
, мы могли бы просто использовать IntStream.rangeClosed(1, 5)
в первую очередь, не имея дело с List
:
IntStream.rangeClosed(1, 5).flatMap(i ->
IntStream.concat(IntStream.range(1, i), IntStream.rangeClosed(i+1, 5))
.map(j -> i*2 + j))
.forEach(System.out::println);