Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Stream sum() short circuiting [duplicate]

While doing a project I wrote this line, basically it decided whether or not to merge the current node based on how many children there are.

int succNodes = Arrays.stream(children).mapToInt(PRQuadNode::count).sum();
if (succNodes <= bucketingParam) { /* do something */ }

The problem is that succNodes will often be significantly larger than bucketingParam. And there's no point to continue counting if I have already found a large enough sum. What would be the best way to enable the stream to stop early, if I knew I was going to fail the check succNodes <= bucketingParam?

Note: In this case children is always of size 4.

Note 2: PRQuadNode::count is a recursive method, it is not tail recursive.

like image 706
Chris Avatar asked Dec 06 '18 20:12

Chris


2 Answers

Actually, Java 9 comes with the takeWhile method, which is a short-circuiting operation of the stream, returning the longest prefix of elements matching the given predicate.

Because the predicate depends on the sum of the previous elements, a wrapper has to be used in order to store the intermediate result. In the example below, I use the AtomicInteger class:

AtomicInteger sum = new AtomicInteger();
Arrays.asList(2, 3, 5, 7, 11, 13, 17).stream()
    .takeWhile(i -> sum.addAndGet(i) < 15)
    .forEach(System.out::println);

Returns:

2
3
5
like image 101
MC Emperor Avatar answered Sep 21 '22 15:09

MC Emperor


You cannot short-circuit the stream pipeline with the use of streams alone.

one workaround (with the use of side-effects) would be:

int[] c = new int[1];
boolean r = Arrays.stream(children).noneMatch(e -> (c[0] = c[0] + e.count()) > bucketingParam);
if (r) { /* do something */ }

This accomplishes the task but unfortunately, is not threadsafe (doubt you're ever going to run this in parallel anyway as your source will always have 4 elements).

An imperative approach would be:

int succNodes = 0;
boolean result = true;
for (PRQuadNode p : children) {
    if (succNodes > bucketingParam) {
         result = false;
         break;
    }
    succNodes += p.count();
}

if (result) { /* do something */ }
like image 38
Ousmane D. Avatar answered Sep 21 '22 15:09

Ousmane D.