Hi I have a List where the data looks like this
[{"month":"April","day":"Friday","count":5},
{"month":"April","day":"Monday","count":6},
{"month":"April","day":"Saturday","count":2},
{"month":"April","day":"Sunday","count":1},
{"month":"April","day":"Thursday","count":7},
{"month":"April","day":"Tuesday","count":8},
{"month":"April","day":"Wednesday","count":10},
{"month":"March","day":"Friday","count":3},
{"month":"March","day":"Monday","count":2},
{"month":"March","day":"Saturday","count":15},
{"month":"March","day":"Sunday","count":11},
{"month":"March","day":"Thursday","count":4},
{"month":"March","day":"Tuesday","count":20},
{"month":"March","day":"Wednesday","count":7},
{"month":"May","day":"Friday","count":2},
{"month":"May","day":"Monday","count":0},
{"month":"May","day":"Saturday","count":7},
{"month":"May","day":"Sunday","count":4},
{"month":"May","day":"Thursday","count":8},
{"month":"May","day":"Tuesday","count":3},
{"month":"May","day":"Wednesday","count":6}]
My object class is
String month;
String day;
Integer count;
What I want to get by using stream is sum of count grouped by month and the day with max count for that month.
so end result will look something like
April, Wednesday, 39
March, Tuesday, 62
May, Thursday , 30
I have been trying to use stream and grouping by but no luck. Any help is appreciated. Thanks
EDIT
Map<String, Integer> totalMap = transactions.stream().collect(Collectors.groupingBy(MonthlyTransaction::getMonth, Collectors.summingInt(MonthlyTransaction::getCount)));
Map<String, String> maxMap = transactions.stream().collect(Collectors.groupingBy(MonthlyTransaction::getMonth)).values().stream().toMap(Object::getDay, Collextions.max(Object::getCount);
obviously the maxMap method is wrong but I do not know how to write it.
If you want to find both the sum of counts per month and the day with the max count per month in a single pass, I think you need a custom collector.
First, let's create a holder class where to store the results:
public class Statistics {
private final String dayWithMaxCount;
private final long totalCount;
public Statistics(String dayWithMaxCount, long totalCount) {
this.dayWithMaxCount = dayWithMaxCount;
this.totalCount = totalCount;
}
// TODO getters and toString
}
Then, create this method, which returns a collector that accumulates both the sum of counts and the max count, along with the day in which that max was found:
public static Collector<MonthlyTransaction, ?, Statistics> withStatistics() {
class Acc {
long sum = 0;
long maxCount = Long.MIN_VALUE;
String dayWithMaxCount;
void accumulate(MonthlyTransaction transaction) {
sum += transaction.getCount();
if (transaction.getCount() > maxCount) {
maxCount = transaction.getCount();
dayWithMaxCount = transaction.getDay();
}
}
Acc merge(Acc another) {
sum += another.sum;
if (another.maxCount > maxCount) {
maxCount = another.maxCount;
dayWithMaxCount = another.dayWithMaxCount;
}
return this;
}
Statistics finish() {
return new Statistics(dayWithMaxCount, sum);
}
}
return Collector.of(Acc::new, Acc::accumulate, Acc::merge, Acc::finish);
}
This uses the local class Acc
to accumulate and merge partial results. The finish
method returns an instance of the Statistics
class, which holds the final results. At the end, I'm using Collector.of
to create a collector based on the methods of the Acc
class.
Finally, you can use the method and class defined above as follows:
Map<String, Statistics> statisticsByMonth = transactions.stream()
.collect(Collectors.groupingBy(MonthlyTransaction::getMonth, withStatistics()));
did this in 2 steps instead of trying to write 1 stream to achieve the result
//First get the total of counts grouping by month
Map<String, Integer> totalMap = transactions.stream()
.collect(Collectors.groupingBy(MonthlyTransaction::getMonth, Collectors.summingInt(MonthlyTransaction::getCount)));
List<MonthlyTransaction> finalStat = new ArrayList<>();
//iterate over the total count map
totalMap.entrySet().stream().forEach(entry -> {
//Using the Stream filter to mimic a group by
MonthlyTransaction maxStat = transactions.stream()
.filter(t -> t.getMonth().equals(entry.getKey()))
//getting the item with the max count for the month
.max(Comparator.comparing(MonthlyTransaction::getCount)).get();
//Setting the count to the total value from the map as the max count value is not a requirement.
maxStat.setCount(entry.getValue());
//add the item to the list
finalStat.add(maxStat);
});
This may not be the best approach to the problem but this gives me the exact result. Thanks to everyone who had a look at it and tried to help.