7

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.

Charlie
  • 1,169
  • 2
  • 16
  • 31
Chris
  • 145
  • 8
  • Per SO policy you Can ask homework questions, if you have made a good faith effort to solve it and your school does not have a policy against posting this. So you should be fine posting what you have (FYI). – Bear Dec 06 '18 at 20:36
  • 1
    You're actually better off doing this without a stream. – Ousmane D. Dec 06 '18 at 21:12

3 Answers3

6

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
MC Emperor
  • 22,334
  • 15
  • 80
  • 130
2

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 */ }
Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
1

One cannot do this with stream. However, you can already filter out if it is greater than bucketingParam. If any of them already greater than bucketingParam there's no point in continuing.

if (Arrays.stream(children).mapToInt(PRQuadNode::count)
                           .noneMatch(x -> x > bucketingParam)) {
    int succNodes = Arrays.stream(children).mapToInt(PRQuadNode::count).sum();
}
fastcodejava
  • 39,895
  • 28
  • 133
  • 186