Home > other >  Obtain the Count per Month from the Count per Day using Java Streams and groupingBy()
Obtain the Count per Month from the Count per Day using Java Streams and groupingBy()

Time:05-31

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 Object as a generic type. Because a collection element of type Object worth nothing without casting. If you have no intention to write muddy code full of casts and intanceof checks, 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 String as 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 with Object. Use an appropriate data type for every element. Why? Because LocalDate, Integer, YearMonth have their own unique behavior that String can'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

A link to Online Demo

  • Related