Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 rewriting a complex for loop using streams

Is it possible to rewrite a complicated for loop like this just using java 8 streams? Anything I come up with seems more bloated, then just leaving the code as is below with a normal for loop.

public static  boolean isBalanced(String text) {
    int count = 0;
    for(int i = 0; i < text.length(); i++ ) {
        if (text.charAt(i) == ')') {
            count--;
        } else if (text.charAt(i) == '(') {
            count++;
        }
        if (count < 0) {
            return false;
        }
    }
    return count == 0;
}


Using Streams

public static boolean isBalanced2(String text) {
    AtomicInteger count = new AtomicInteger(0);

    text.chars()
        .forEachOrdered(x -> {
             if (x == ')') {
                 count.getAndDecrement();
             } else if (x == '(') {
                 count.getAndIncrement();
             }
        });

    return count.get() == 0;
}

It works ok but it iterates through the whole string, when sometimes that might be wasted computation for example in the case of the string ")......"

It doesn't seem possible to exit the stream as soon as count is < 0 ? (And I dont want to throw an exception!)

Thanks

like image 229
Pat Avatar asked Mar 23 '26 17:03

Pat


2 Answers

You should not.

Lambda and Stream are not replacement for all complicated for loop. While you may use a Stream as you've done, this does not means it is better for the eye (what is easier to understand?) and for performance (you surely lost something due to the AtomicInteger vs int based operation but you could probably use a int[] array instead).

  • You can't exit the loop as soon as possible, unless you use exception, but you can narrow your test a little (and you should bench it). You could probably think of using filter after map operation but that won't make it easier to read.
  • You should probably stick to pure function, eg: you should probably not have a side effect (on the AtomicInteger).
like image 120
NoDataFound Avatar answered Mar 26 '26 07:03

NoDataFound


The following code will do what you want, and is smaller than your original code, but it's convoluted and always processes all characters, i.e. doesn't stop early if unbalanced ) is detected.

However, unlike some other answers here, it doesn't violate the stream rules by maintaining state outside the stream.

private static boolean isBalanced(String text) {
    return 0 == text.chars()
            .reduce(0, (n, c) -> n < 0 ? n : c == '(' ? n + 1 : c == ')' ? n - 1 : n);
}

The logic is as follows:

  • Keep a running total representing the nesting level, i.e. increment the value when ( is found and decrement the value when ) is found.

  • If the total goes below 0, stop updating it, i.e. when an unbalanced ) is found, keep final total at -1.

The result of the reduce operation is then:

  • 0: All ( have balanced )

  • -1: Found unbalanced )

  • >0: Found unbalanced (

Long version of the same code, using if statements instead of conditional ternary operator.

private static boolean isBalanced(String text) {
    int finalLevel = text.chars().reduce(0, (lvl, ch) -> {
        if (lvl < 0)
            return lvl; // Keep result of -1 for unbalanced ')'
        if (ch == '(')
            return lvl + 1;
        if (ch == ')')
            return lvl - 1;
        return lvl;
    });
    return (finalLevel == 0);
}
like image 37
Andreas Avatar answered Mar 26 '26 07:03

Andreas



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!