2015-03-14 5 views
2

Использование java 8 Я хочу создать новую коллекцию из списка и накопить сумму на этом пути.Java 8 - накапливать и создавать новую коллекцию

Список источников состоит из объектов, которые выглядят примерно так:

class Event { 
    String description; 
    double sum; 
} 

С примерным списком, как это:

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

В результате список должен выглядеть следующим образом

  • desc1, 10.0, 10.0
  • desc2, 14.0, 24.0
  • DESC3, 5,0, 29,0

Я знаю, как подвести итоги, чтобы получить окончательную сумму, в этом случае 29,0, но я хочу, чтобы создать список результатов и в то же время, накапливают сумму по пути ,

Как это сделать с помощью Java8?

+0

Как бы вы сделали только один из элементов списка результатов? –

ответ

4

Вы можете сделать это, реализовав свой собственный коллектор для выполнения сопоставления и суммирования. Ваш потоковый код будет выглядеть следующим образом:

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))); 

Для этого я предполагал, новый класс SummedEvent который также держит нарастающий итог. Ваш класс коллектора будет затем реализовать что-то вроде этого:

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; 
    } 
} 
+2

Обратите внимание, что это не сработает, если конвейер работает параллельно –

+1

... думая об этом, я не уверен, что это можно сделать параллельно за один проход. –

+0

Я вообще не вижу, как это можно было бы сделать параллельно, так как это требует упорядочения, я полагаю, что ОП знал об этом, хотя – tddmonkey

3

Если вы будете запускать трубопровод последовательно, вы можете использовать этот маленький хак с peek.

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

Имейте в виду, что вы получите неправильные результаты, если поток работают параллельно.

Однако я не уверен, что если трубопровод может работать параллельно в один проход, но предполагая, что базовый список имеет быстрый доступ к элементу по индексу i вы можете сделать это следующим образом:

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 применит сокращение, которое вы ищете для сумм. Затем вам просто нужно передать индексы и сопоставить каждое событие с соответствующей накопленной суммой.

+4

Не называйте это кодом (где «это» означает написание кода, который будет бесшумно ломаться, если кто-нибудь когда-нибудь превратит ваш поток параллельно). Это версия кодирования 2015 года с расчетом данных, потому что «я никогда не буду использовать потоки». –

+0

@BrianGoetz Вы имеете в виду версию «peek»? Да, я не люблю использовать его в любом случае, поскольку это часто является признаком побочных эффектов; возможно, я был недостаточно ясен в своем ответе, я отредактировал это. –

+0

@ AlexisC. Означает ли это, что вариант parallelPrefix не имеет проблем при работе в параллели? – svaret

Смежные вопросы