Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Equivalent of Scala's foldLeft in Java 8

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 accumulator: new StringBuilder("")

Thus, could anyone point me to the proper equivalent of the foldLeft/fix my code?

like image 855
GA1 Avatar asked Dec 20 '16 10:12

GA1


People also ask

What is the difference between a regular reduction and a collector?

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.

What does foldLeft return?

Here, foldLeft method takes associative binary operator function as a parameter. This method returns the result as value.

What is foldRight in Scala?

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.


2 Answers

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.

like image 154
dzs Avatar answered Sep 18 '22 14:09

dzs


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.

like image 38
Lachezar Balev Avatar answered Sep 19 '22 14:09

Lachezar Balev