svaret
svaret

Reputation: 335

Java 8 - accumulate and create new collection

Using java 8 I want to create a new collection from a list and accumulate a sum along the way.

The source list consists of objects that look something like this:

class Event {
  String description;
  double sum;
}

With an example list like this:

{ { "desc1", 10.0 }, {"desc2", 14.0 }, {"desc3", 5.0 } }

The resulting list should look like this

I know how to sum up to get a final sum, in this case 29.0, but I want to create the result list and at the same time accumulate the sum along the way.

How can I do this with Java8?

Upvotes: 2

Views: 4095

Answers (2)

Alexis C.
Alexis C.

Reputation: 93842

If you will run your pipeline sequentially, you can use this little hack with peek.

double[] acc = {0};
List<CustomEvent> list = originalList.stream()
                                     .peek(e -> acc[0] += e.sum)
                                     .map(e -> new CustomEvent(e, acc[0]))
                                     .collect(toList());

Be aware that you'll get wrong results if the stream is run in parallel.

However I'm not sure if the pipeline can be run in parallel in one pass, but assuming the underlying list has a fast access to element at index i you can do it like this:

double[] acc = originalList.stream().mapToDouble(e -> e.sum).toArray();
Arrays.parallelPrefix(acc, Double::sum);

List<CustomEvent> lx = IntStream.range(0, originalList.size())
                                .parallel()
                                .mapToObj(i -> new CustomEvent(originalList.get(i), acc[i]))
                                .collect(toList());

parallelPrefix will apply the reduction you are looking for the sums. Then you just have to stream the indices and you map each event to its corresponding accumulated sum.

Upvotes: 3

tddmonkey
tddmonkey

Reputation: 21184

You could do this by implementing your own collector to perform the mapping and summing together. Your streaming code would look like this:

List<SummedEvent> summedEvents = events.stream().collect(EventConsumer::new, EventConsumer::accept, EventConsumer::combine).summedEvents();
summedEvents.forEach((se) -> System.out.println(String.format("%s, %2f, %2f", se.description, se.sum, se.runningTotal)));

For this I've assumed a new class SummedEvent which also holds the running total. Your collector class would then be implemented something like this:

class EventConsumer {
    private List<SummedEvent> summedEvents = new ArrayList<>();
    private double runningTotal = 0;

    public void accept(Event event) {
        runningTotal += event.sum;
        summedEvents.add(new SummedEvent(event.description, event.sum, runningTotal));
    }
    public void combine(EventConsumer other) {
        this.summedEvents.addAll(other.summedEvents);
        this.runningTotal += other.runningTotal;
    }

    public List<SummedEvent> summedEvents() {
        return summedEvents;
    }
}

Upvotes: 4

Related Questions