What is the equivalent of of Scala's great foldLeft
in Java 8?
I was tempted to think it was reduce
, but reduce has to return something of identical type to what it reduces on.
Example:
import java.util.List; public class Foo { // this method works pretty well public int sum(List<Integer> numbers) { return numbers.stream() .reduce(0, (acc, n) -> (acc + n)); } // this method makes the file not compile public String concatenate(List<Character> chars) { return chars.stream() .reduce(new StringBuilder(""), (acc, c) -> acc.append(c)).toString(); } }
The problem in the code above is the acc
umulator: new StringBuilder("")
Thus, could anyone point me to the proper equivalent of the foldLeft
/fix my code?
The normal reduction is meant to combine two immutable values such as int, double, etc. and produce a new one; it's an immutable reduction. In contrast, the collect method is designed to mutate a container to accumulate the result it's supposed to produce.
Here, foldLeft method takes associative binary operator function as a parameter. This method returns the result as value.
Advertisements. foldRight() method is a member of TraversableOnce trait, it is used to collapse elements of collections. It navigates elements from Right to Left order.
There is no equivalent of foldLeft
in Java 8's Stream API. As others noted, reduce(identity, accumulator, combiner)
comes close, but it's not equivalent with foldLeft
because it requires the resulting type B
to combine with itself and be associative (in other terms, be monoid-like), a property that not every type has.
There is also an enhancement request for this: add Stream.foldLeft() terminal operation
To see why reduce won't work, consider the following code, where you intend to execute a series of arithmetic operations starting with given number:
val arithOps = List(('+', 1), ('*', 4), ('-', 2), ('/', 5)) val fun: (Int, (Char, Int)) => Int = { case (x, ('+', y)) => x + y case (x, ('-', y)) => x - y case (x, ('*', y)) => x * y case (x, ('/', y)) => x / y } val number = 2 arithOps.foldLeft(number)(fun) // ((2 + 1) * 4 - 2) / 5
If you tried writing reduce(2, fun, combine)
, what combiner function could you pass that combines two numbers? Adding the two numbers together clearly does not solve it. Also, the value 2
is clearly not an identity element.
Note that no operation that requires a sequential execution can be expressed in terms of reduce
. foldLeft
is actually more generic than reduce
: you can implement reduce
with foldLeft
but you cannot implement foldLeft
with reduce
.
Update:
Here is initial attempt to get your code fixed:
public static String concatenate(List<Character> chars) { return chars .stream() .reduce(new StringBuilder(), StringBuilder::append, StringBuilder::append).toString(); }
It uses the following reduce method:
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
It may sound confusing but if you look at the javadocs there is a nice explanation that may help you quickly grasp the details. The reduction is equivalent to the following code:
U result = identity; for (T element : this stream) result = accumulator.apply(result, element) return result;
For a more in-depth explanation please check this source.
This usage is not correct though because it violates the contract of reduce which states that the accumulator should be an associative, non-interfering, stateless function for incorporating an additional element into a result. In other words since the identity is mutable the result will be broken in case of parallel execution.
As pointed in the comments below a correct option is using the reduction as follows:
return chars.stream().collect( StringBuilder::new, StringBuilder::append, StringBuilder::append).toString();
The supplier StringBuilder::new
will be used to create reusable containers which will be later combined.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With