I want to transform a list with per-day sales (whatever, doesn't matter) into a list of per-month sales.
So I have a List<Map<String, Object>> perDay, which contains these daily values:
[{DATE=2022-01-01, COUNT=5}, {DATE=2022-01-02, COUNT=3}, {DATE=2022-02-29, COUNT=4}]
These values should be gathered into monthly values, like that:
[{DATE=2022-01, COUNT=8}, {DATE=2022-02, COUNT=4}]
I want to use groupingBy() method with streams.
Here's my code:
List<Map<String, Object>> perDay = new ArrayList<>();
Map<String, Object> jan1 = new HashMap<>();
jan1.put("DATE", "2022-01-01"); jan1.put("COUNT", "5");
Map<String, Object> jan2 = new HashMap<>();
jan2.put("DATE", "2022-01-02"); jan2.put("COUNT", "3");
Map<String, Object> feb1 = new HashMap<>();
feb1.put("DATE", "2022-02-29"); feb1.put("COUNT", "4");
Map<String, Object> perMonth = perDay.stream()
.collect(Collectors.groupingBy("???",
Collectors.summingInt(f -> Integer.parseInt((String) f))));
I guess, I'm close, but I'm not quite there yet.
Any ideas?
CodePudding user response:
assuming that your dates are always in format
2022-01-01
You could try:
var map = perDay.stream().collect(
Collectors.groupingBy(monthMap -> ((String) monthMap.get("DATE")).substring(0, 7),
Collectors.summingInt(monthMap -> Integer.parseInt(String.valueOf(monthMap.get("COUNT"))))));
Returns:
{2022-01=8, 2022-02=4}
CodePudding user response:
The approach you've introduced is not maintainable and as a consequence prone to errors and code duplications, and also makes extension of the code harder.
These are the main issues:
Don't use
Objectas a generic type. Because a collection element of typeObjectworth nothing without casting. If you have no intention to write muddy code full of casts andintanceofchecks, don't take this rode.Map- is not the same thing as JSON-object (I'm not talking about classes from Java-libraries like Gson, they are map-based), don't confuse a way of structuring the human-readable text with the implementation of the data structure.Keys like
"DATE"and"COUNT"are useless - you can simply store the actual date and count instead.Using
Stringas a type for numbers and dates doesn't give you any advantage. There's nothing you can do with them (apart from comparing and printing on the console) without parsing, almost like withObject. Use an appropriate data type for every element. Why? BecauseLocalDate,Integer,YearMonthhave their own unique behavior thatStringcan't offer you.
That's how you can improve your code by tuning the collection representing sales per day List<Map<String, Object>> into a Map<LocalDate, Integer> which is way more handy:
Map<LocalDate, Integer> salesPerDay = perDay.stream()
.map(map -> Map.entry(LocalDate.parse((String) map.get("DATE"), DateTimeFormatter.ISO_DATE),
Integer.parseInt((String) map.get("COUNT"))))
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.summingInt(Map.Entry::getValue)));
And that's how you can produce a map containing sales per month from it (YearMonth from the java.time package is used as a key):
Map<YearMonth, Integer> salesPerMonth = salesPerDay.entrySet().stream()
.collect(Collectors.groupingBy(
entry -> YearMonth.from(entry.getKey()),
Collectors.summingInt(Map.Entry::getValue)));
salesPerMonth.forEach((k, v) -> System.out.println(k " -> " v));
Output:
2022-01 -> 8
2022-02 -> 4
