For this greatly simplified example of my problem, I have a Stat
object with a year
field and three other statistics fields. Imagine they are the yearly statistics for number of patients of each animal type from branches of a veterinarian chain, and I want to get sums for all branches by year.
In other words, from a list of Stat
objects, I'd like to return a Map<Integer, Stat>
, where the integer is the year, and the Stat object has the year and sums for each of the four fields.
public class Stat
{
int year;
public int getYear() { return year; }
long cats;
public long getCats() { return cats; }
long dogs;
public long getDogs() { return dogs; }
long pigeons;
public long getPigeons() { return pigeons; }
public Stat(int year, long cats, long dogs, long pigeons)
{
this.year = year;
this.cats = cats;
this.dogs = dogs;
this.pigeons = pigeons;
}
public Stat(Stat left, Stat right)
{
if (left.year != right.year)
throw new IllegalArgumentException("Only allow combining for same year.");
this.year = left.year;
this.cats = left.cats + right.cats;
this.dogs = left.dogs + right.dogs ;
this.pigeons = left.pigeons + right.pigeons;
}
@Override
public String toString()
{
return String.format("%d c=%d d=%d p=%d", year, cats, dogs, pigeons);
}
}
@Test
public void testStat()
{
List<Stat> items = Arrays.asList(
new Stat(2017, 5, 8, 12),
new Stat(2017, 123, 382, 15),
new Stat(2018, 1, 2, 3)
);
Map<Integer, Optional<Stat>> result = items.stream()
.collect(Collectors.groupingBy(Stat::getYear,
Collectors.reducing(Stat::new)
));
System.out.println(result);
}
The Optional
is unnecessary, since groupingBy
would never create a List
that needs reducing
if there were no elements.
Is there a way to get Map<Integer, Stat>
, preferably without having to create a blank "identity" object?
If I have to resort to creating an identity creation function to reducing
, the Stat object's combining constructor has to have a year (see the constructor), so how does the identity constructor get the year passed to it?
You could rather achieve this using Collectors.toMap
as :
Map<Integer, Stat> result = items.stream()
.collect(Collectors.toMap(Stat::getYear,
Function.identity(), (one, another) -> sumStatsOfSameYear(one, another)));
where sumAttributes
is defined as
// stat from the same year
private static Stat sumStatsOfSameYear(Stat one, Stat another) {
new Stat(one.getYear(), one.getCats() + another.getCats(),
one.getDogs() + another.getDogs(), one.getPigeons() + another.getPigeons()))
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With