Search code examples
functional-programmingjava-8reduce

Is it possible to iterate a stream only once and perform 2 or more operations?


Given the code

List<Integer> numbers = Arrays.asList(2, 4, 3);

int sumTotal = numbers.stream().reduce(-3, (x, y) -> x + y + 3);
int multiplyTotal = numbers.stream().reduce(1, (x, y) -> x * y);

Is it possible to perform both operations while iterating the stream only once?

Also note each reduce has a different identity: -3 and 1.


Solution

  • You can create a custom class, and use a mutable reduction:

    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(2, 4, 3);
    
        Result result = numbers.stream().collect(Result::new, Result::consume, Result::combine);
        System.out.println("result = " + result);
    }
    
    private static class Result {
        private int sum = 0;
        private int product = 1;
    
        public void consume(int i) {
            sum += i + 3;
            product *= i;
        }
    
        public void combine(Result r) {
            // READ note below
            sum += r.sum + 3;
            product *= r.product;
        }
    
        @Override
        public String toString() {
            return "Result{" +
                "sum=" + sum +
                ", product=" + product +
                '}';
        }
    }
    

    But I doubt you will gain much by doing that over simply iterating twice as you're doing.

    EDIT:

    Note that the combine() method above, invoked when using a parallel stream, will produce results that are inconsistent with a sequential stream. The problem is caused, as Holger mentions in the comments, by the fact that the operation doesn't respect the identity preconditions of a reduction: applying the reduction on any value V with the identity is supposed to produce V, but it produces V + 3 with 0 as identity.

    Make sure not to use a parallel stream to compute that result.