It's worth mentioning that your imperative style implementation is probably the most effective way to express your expectations. But if you really want to implement same logic using Java 8 Stream API, you can consider utilizing .reduce() method, e.g.
import org.apache.commons.lang3.tuple.Pair;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
final class SummingPairsLookAheadExample {
public static void main(String[] args) {
final int[] data = new int[]{1,2,3,4,5,6};
final int sum = 8;
final Set<Pair> pairs = Arrays.stream(data)
.boxed()
.parallel()
.reduce(
Pair.of(Collections.synchronizedSet(new HashSet<Pair>()), Collections.synchronizedSet(new HashSet<Integer>())),
(pair,el) -> doSumming(pair, el, sum),
(a,b) -> a
).getLeft();
System.out.println(pairs);
}
synchronized private static Pair<Set<Pair>, Set<Integer>> doSumming(Pair<Set<Pair>, Set<Integer>> pair, int el, int sum) {
if (pair.getRight().contains(el)) {
pair.getLeft().add(Pair.of(el, sum - el));
}
pair.getRight().add(sum - el);
return pair;
}
}
Output
[(5,3), (6,2)]
The first parameter in .reduce() method is accumulator's initial value. This object will be passed to each iteration step. In our case we use a pair of Set<Pair> (expected result) and Set<Integer> (same as variable lookaheads in your example). Second parameter is a lambda (BiFunction) that does the logic (extracted to a separate private method to make code more compact). And the last one is binary operator. It's pretty verbose, but it does not rely on any side effects. @Eugene pointed out that my previous example had issues with parallel execution, so I've updated this example to be safe in parallel execution as well. If you don't run it in parallel you can simply remove synchronized keyword from helper method and use regular sets instead of synchronized one as initial values for accumulator.