Oleg Mikhailov
Oleg Mikhailov

Reputation: 6081

Java 8 streams: process every possible pair of elements from list

I have a Collection of elements of an arbitrary class. I want to iterate through the collection and perform some operation using the element and each other element of the collection one-by-one (excluding the element itself). Let it be List<Integer> for simplicity:

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

With for loops it will be:

for (Integer i : list) {
    for (Integer j : list) {
        if (!i.equals(j)) System.out.println(i * 2 + j);  //just for example
    }
}

The question is how to do it with the Stream API?

That's what I have come to:

list.stream().forEach(i ->
    list.stream().forEach(j -> {
        if (!i.equals(j)) System.out.println(i * 2 + j);
    })
);

It doesn't look better than nested loop though. Is there a more elegant way?

Upvotes: 15

Views: 4722

Answers (2)

Holger
Holger

Reputation: 298459

You can avoid object comparisons by mapping to int values beforehand, e.g.

list.stream().mapToInt(Integer::intValue)
    .flatMap(i -> list.stream().filter(j -> j!=i).mapToInt(j -> i*2 + j))
    .forEach(System.out::println);

But actually, you are performing conditional operations and equality checks for something that is invariant. As the whole operation relies on the source list not changing its contents in-between, you can simply generate the pairing list indices in the first place:

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

In case you don’t want to calculate a numeric value but create an object for the pairs, it gets a bit more complicated due to the fact that IntStream has no flatMapToObj operation, so we need a combination of mapToObj and flatMap (unless we use boxed). So constructing a two-length array exemplary looks like:

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

Of course, for a list as simple as [ 1, 2, 3, 4, 5 ], we could just use IntStream.rangeClosed(1, 5) in the first place, without dealing with a 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);

Upvotes: 6

Tunaki
Tunaki

Reputation: 137209

You can do this using the flatMap operation:

list.stream()
    .flatMap(i -> list.stream().filter(j -> !i.equals(j)).map(j -> i * 2 + j))
    .forEach(System.out::println);

This code is creating a Stream of the input list. It flat maps each element of the list with a stream created by the same list, where the current element was filtered out, and each element of this new list is the result of the i * 2 + j operation.

All the elements are then printed to the console.

Upvotes: 15

Related Questions