Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing a collection using a reduce (3 parameters) function - streams java 8

I am trying to calculate the multiplication of a value using the previous two values using java 8's stream. I want to call a function that will return an array/list/collection. I am creating a List and adding 1,2 to it.

Let's say the list name is result.

public static void main (String[] args) { 
List<Integer> result = new ArrayList<Integer>();
result.add(1);
result.add(2);
int n = 5; //n can be anything, choosing 5 for this example
res(n, result);
//print result which should be [1, 2, 2, 4, 8]
}
public static List<Integer> res(int n, List<Integer> result ) {
           result.stream()
                 .limit(n)
                 .reduce(identity, (base,index) -> base);

       //return result;
}

Now the issue is trying to try to pass result into the stream to keep updating the list with the new values using the stream. According to the java tutorials, it is possible, albeit inefficient.

"If your reduce operation involves adding elements to a collection, then every time your accumulator function processes an element, it creates a new collection that includes the element, which is inefficient."

Do I need to use the optional third parameter, BinaryOperator combiner, to combine the list + result??

<U> U reduce(U identity,
         BiFunction<U,? super T,U> accumulator,
         BinaryOperator<U> combiner)

In short; I want to pass a list with two values and have the function find the multiplication of the first two values (1,2), add it to the list, and find the multiplication of the last two values (2,2), and add it to the list, and until the stream hits the limit.

like image 357
user3749140 Avatar asked Sep 17 '14 00:09

user3749140


1 Answers

It looks like you're trying to implement a recurrence relation. The reduce method applies some function to a bunch of pre-existing values in the stream. You can't use reduce and take an intermediate result from the reducer function and "feed it back" into the stream, which is what you need to do in order to implement a recurrence relation.

The way to implement a recurrence relation using streams is to use one of the streams factory methods Stream.generate or Stream.iterate. The iterate factory seems to suggest the most obvious approach. The state that needs to be kept for each application of the recurrence function requires two ints in your example, so unfortunately we have to create an object to hold these for us:

static class IntPair {
    final int a, b;
    IntPair(int a_, int b_) {
        a = a_; b = b_;
    }
}

Using this state object you can create a stream that implements the recurrence that you want:

Stream.iterate(new IntPair(1, 2), p -> new IntPair(p.b, p.a * p.b))

Once you have such a stream, it's a simple matter to collect the values into a list:

List<Integer> output =
    Stream.iterate(new IntPair(1, 2), p -> new IntPair(p.b, p.a * p.b))
          .limit(5)
          .map(pair -> pair.a)
          .collect(Collectors.toList());
System.out.println(output);

[1, 2, 2, 4, 8]

As an aside, you can use the same technique to generate the Fibonacci sequence. All you do is provide a different starting value and iteration function:

Stream.iterate(new IntPair(0, 1), p -> new IntPair(p.b, p.a + p.b))

You could also implement a similar recurrence relation using Stream.generate. This will also require a helper class. The helper class implements Supplier of the result value but it also needs to maintain state. It thus needs to be mutable, which is kind of gross in my book. The iteration function also needs to be baked into the generator object. This makes it less flexible than the IntPair object, which can be used for creating arbitrary recurrences.

like image 70
Stuart Marks Avatar answered Sep 18 '22 12:09

Stuart Marks