Sebastian
Sebastian

Reputation: 907

Is it possible to calculate values in a stream based on themselves

If I have a java stream of e.g. numbers, is it possible to calculate e.g. the sums up to that number (by adding the number to the "previously" calculated sum)?

e.g. (1, 2, 3, 5, 7, 9, 0) --> (1, 3, 6, 11, 18, 27, 27)

Upvotes: 3

Views: 787

Answers (4)

adarsh
adarsh

Reputation: 1503

A simple solution using Streams is to use reducing. This solution is not stateless, though.

var inputList = List.of(1, 2, 3, 5, 7, 9, 0);
var result = new ArrayList<>();

var unused = inputList.stream().collect(reducing(0, (a, b) -> {
    result.add(a + b);
    return a + b;
}));

System.out.println(result); // [1, 3, 6, 11, 18, 27, 27]

A few points:

  • Mutation should always be avoided while using streams. Streams are not meant to replace imperative code.
  • Above solution doesn't have room for parallelism
  • Streams API introduces the functional programming (what to do) paradigm in Java. It is not supposed to be mixed with imperative (how to do) code.
  • The parallelPrefix solution in Faeemazaz Bhanej answer should be the preferred solution

Upvotes: 1

Faheem azaz Bhanej
Faheem azaz Bhanej

Reputation: 2396

You have to use parallelPrefix for sum of array in java stream.

Integer[] arr = {1, 2, 3, 5, 7, 9, 0};
Arrays.parallelPrefix(arr, (x, y) -> x + y);
System.out.println(Arrays.toString(arr));

You have to use AtomicInteger for ArrayList

List<Integer> list = new ArrayList<>();
list.addAll(Arrays.asList(1, 2, 3, 5, 7, 9, 0));
        
AtomicInteger ai = new AtomicInteger();
List<Integer> sumOfList = list.stream()
                              .map(ai::addAndGet)
                              .collect(Collectors.toList());
System.out.println(sumOfList);

Upvotes: 4

michid
michid

Reputation: 10834

Here is a proof of concept building on this answer. It implements a fully stateless solution with O(n) time complexity that also works with parallel streams.

    record CumSum(Integer acc, List<Integer> sums) {
        public CumSum() {
            this(0, List.of());
        }

        public CumSum collect(Integer n) {
            return new CumSum(acc + n, concat(sums, Stream.of(acc + n)));
        }

        public CumSum combine(CumSum cumSum) {
            return new CumSum(acc + cumSum.acc, concat(sums, cumSum.sums.stream().map(n -> acc + n)));
        }

        private static List<Integer> concat(List<Integer> sums, Stream<Integer> acc) {
            return Stream.concat(sums.stream(), acc).toList();
        }
    }

    var list1 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    var list2 = list1.stream().parallel()
        .reduce(new CumSum(), CumSum::collect, CumSum::combine)
        .sums;

    System.out.println(list2);

The output is

[1, 3, 6, 10, 15, 21, 28, 36, 45, 55]

regardless of using a parallel stream or not.

Note that due to the lack of a true immutable List in Java, CumSum jumps back and forth from List and Stream, which complicates this implementation unnecessarily. Also the separate tracking in acc could be replaced by just looking at the last element in sums. For more clarity I chose not to do that.

Upvotes: 0

Saginatio
Saginatio

Reputation: 99

yes, you can use a custom collector to do that. Although the "stream" part would have nothing to do.

It would look like list.stream().collect( new CustomSequentialCollector() )

Upvotes: -1

Related Questions