Reputation: 6081
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
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
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