dictionarysumjava-streamnested-map

Java - How to create a new map containing summed fields from a map containing a key and list of fields


I am provided with a List<Result>

The Result class has a number of fields of which five are of interest

class Result {
   
    public enum SESSION_TYPE {
        NIGHT,
        DAY
    }

    private SESSION_TYPE sessionType;
    private String sessionDateTime;
    private String sessionDate;
    private Double consumed;
    private Double cost;

    ...
    getters and setters 

}
  

I have created a map as follows


Map<String,List<Result>> dailyResults = results.stream().collect(Collectors.groupingBy(Result::getSessionDate));

I would now like to create a new map keyed on the same sessionDate which contains the summed consumed and cost fields grouped by the SESSION_TYPE, thus

Map<String, Map<String,SessionDetail>> sessionResults

i.e., Map<SessionDate, Map<SessionType,SessionDeatail>>

where the SessionDetail is a record as follows -

record SessionDetail(Result.SESSION_TYPE sessionType, Double consumedTotal, Double costTotal) {};

I've spent some time on this without achieving the desired result. Any help would be much appreciated.


Solution

  • You can do as follows:

    Here is some data.

    List<Result> results = new ArrayList<>(List.of(
            new Result(Result.SESSION_TYPE.DAY, "T1", "27-Dec-2022", 10., 1.),
            new Result(Result.SESSION_TYPE.DAY, "T1", "27-Dec-2022", 10., 2.),
            new Result(Result.SESSION_TYPE.DAY, "T1", "27-Dec-2022", 10., 3.),
            new Result(Result.SESSION_TYPE.NIGHT, "T1", "26-Dec-2022", 10., 20.),
            new Result(Result.SESSION_TYPE.NIGHT, "T1", "26-Dec-2022", 10., 30.),
            new Result(Result.SESSION_TYPE.DAY, "T1", "26-Dec-2022", 10., 40.),
            new Result(Result.SESSION_TYPE.DAY, "T1", "27-Dec-2022", 10., 10.),
            new Result(Result.SESSION_TYPE.DAY, "T1", "27-Dec-2022", 10., 10.)));
    

    The original results can be first categorized by the Date. Then using toMap, take the session type as the key and then create a new instance, summing the values in the previous instance to the one just encountered.

    Map<String, Map<Result.SESSION_TYPE, SessionDetail>> dailyResults = results
            .stream()
            .collect(Collectors.groupingBy(Result::getSessionDate,
                    Collectors.toMap(Result::getSessionType,
                            r -> new SessionDetail(r.getSessionType(),
                                    r.getConsumed(), r.getCost()),
                            (a, b) -> new SessionDetail(b.sessionType,
                                    a.consumedTotal + b.consumedTotal,
                                    a.costTotal + b.costTotal))));
    

    Print the results as follows:

    dailyResults.entrySet().forEach(e -> {
        System.out.println(e.getKey());
        e.getValue().entrySet()
                .forEach(s -> System.out.println("    " + s));
    });
    

    prints

    27-Dec-2022
        DAY=SessionDetail[sessionType=DAY, consumedTotal=50.0, costTotal=26.0]
    26-Dec-2022
        NIGHT=SessionDetail[sessionType=NIGHT, consumedTotal=20.0, costTotal=50.0]
        DAY=SessionDetail[sessionType=DAY, consumedTotal=10.0, costTotal=40.0]
    
    

    The above works without making any changes to your classes. Since record values are final a new record much be created to replace the previous one using the accumulated sums.