I have a list of object sorted on a month-year String
attribute.
My object class defination looks like:
public class Obj {
String year;
Long membercount;
Long nonmembercount;
Double memberpayment;
Double nonmemberpayment;
}
new Obj("9-2015",100,20,10,5),
new Obj("10-2015",220,40,20,55),
new Obj("11-2015",300,60,30,45),
new Obj("12-2015",330,30,50,6),
new Obj("1-2016",100,10,10,4)
)
I want to do cumulative sum on membercount
, nonmembercount
, memberpayment
, and nonmemberpayment
.
So my new List
of objects would be like below:
new Obj("9-2015",100,20,10,5)
new Obj("10-2015",320,60,30,60)
new Obj("11-2015",620,120,60,105)
new Obj("12-2015",950,150,110,111)
new Obj("1-2016",1050,160,120,115)
I tried with Collectors.summingDouble
but it gives me all sum not cumulative.
I would really appreciate any pointers.
There is no direct support for cumulative operations in the Stream API, though it would be possible to implement such an operation via a custom Collector
. But it’s worth noting that there is already a direct support for such operations on arrays, which might be sufficient for your case:
Extending you sketch of Obj
to
public class Obj {
String year;
Long membercount;
Long nonmembercount;
Double memberpayment;
Double nonmemberpayment;
public Obj(String year, long membercount, long nonmembercount,
double memberpayment, double nonmemberpayment) {
this.year = year;
this.membercount = membercount;
this.nonmembercount = nonmembercount;
this.memberpayment = memberpayment;
this.nonmemberpayment = nonmemberpayment;
}
@Override
public String toString() {
return "Obj("+year+", "+membercount+", "+nonmembercount
+", "+memberpayment+", "+nonmemberpayment+')';
}
}
the solution can look like:
// test data
List<Obj> list=Arrays.asList(
new Obj("9-2015", 100, 20, 10, 5),
new Obj("10-2015", 220, 40, 20, 55),
new Obj("11-2015", 300, 60, 30, 45),
new Obj("12-2015", 330, 30, 50, 6),
new Obj("1-2016", 100, 10, 10, 4));
// creating an array as need for the operation, it will contain the
// result afterwards, whereas the source list is not modified
Obj[] array = list.toArray(new Obj[0]);
// the actual operation
Arrays.parallelPrefix(array, (a,b) -> new Obj(b.year,
a.membercount + b.membercount,
a.nonmembercount + b.nonmembercount,
a.memberpayment + b.memberpayment,
a.nonmemberpayment + b.nonmemberpayment
));
// just print the result
Arrays.asList(array).forEach(System.out::println);
the last line will print
Obj(9-2015, 100, 20, 10.0, 5.0)
Obj(10-2015, 320, 60, 30.0, 60.0)
Obj(11-2015, 620, 120, 60.0, 105.0)
Obj(12-2015, 950, 150, 110.0, 111.0)
Obj(1-2016, 1050, 160, 120.0, 115.0)
While this operation is unlikely to benefit from parallel processing for this small number of elements, there is unfortunately no sequential version of this operation. So you might consider using an ordinary loop solution instead…
For completeness, here is a Stream Collector based solution for cumulative operations. Like with Arrays.parallelPrefix
, the update function must be side-effect-free and associative, which is the case for a function returning a new object with summed up properties.
public static <T> Collector<T,?,List<T>> cumulative(BinaryOperator<T> update) {
return Collector.of(ArrayList::new,
(l,o) -> {
if(!l.isEmpty()) o=update.apply(l.get(l.size()-1), o);
l.add(o);
},
(l,m) -> {
if(l.isEmpty()) return m;
if(!m.isEmpty()) {
T a = l.get(l.size()-1);
for(T b: m) l.add(update.apply(a, b));
}
return l;
});
}
using it with the setup as above:
List<Obj> result = list.stream().collect(cumulative((a,b) -> new Obj(b.year,
a.membercount + b.membercount,
a.nonmembercount + b.nonmembercount,
a.memberpayment + b.memberpayment,
a.nonmemberpayment + b.nonmemberpayment
)));