Подтвердить что ты не робот

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);
    })
);

Это не выглядит лучше, чем вложенный цикл. Есть ли более элегантный способ?

4b9b3361

Ответ 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);