首页 > 解决方案 > 如何使用 Java 8 收集器对三重嵌套地图求和

问题描述

我有这张地图 Map<LocalDate, Map<Integer, Map<EHourQuarter, Double>>>

EHourQuarter 是一个枚举:

public enum EHourQuarter {
    FIRST(0, 14, 15),
    SECOND(15, 29, 30),
    THIRD(30, 44, 45),
    FOURTH(45, 59, 60);

    private Integer start;
    private Integer end;
    private Integer value;//this is for UI purposes
}

使用如下值:{2020-07-07 -> {0 -> {EHourQuarter.FIRST -> 5.5, EHourQuarter.SECOND -> 10.2, ...}, 1 -> {EHourQuarter.FIRST -> 33.2, EHourQuarter.SECOND -> 30.1, ...}, ...},

2020-07-08 -> {0 -> {EHourQuarter.FIRST -> 5.5, EHourQuarter.SECOND -> 10.2, ...}, 1 -> {EHourQuarter.FIRST -> 33.2, EHourQuarter.SECOND -> 30.1, . ..}, ... }

它是 Double 的 EHourQuarter 地图的 Integer 地图的 LocalDate 地图(小时:从 0 到 23)。

我需要得到一个包含每个日期累积的 Map<Integer(hour), Map<EHourQuarter, Double>>,这意味着如果日期 2020-07-07 到 2020-07-10(4 天)包含在小时 0每个 EHourQuarter 每一个 5,那么结果应该显示在 0 小时,每个季度的值为 20。

此外,如果通过这样做,您还可以帮助我将其映射到像这样的 DTO 列表,

public class QuarterlyOccupancyDTO {
    private Integer hour;
    private Integer minute;//this is the value property of EHourQuarter
    private Double occupancy;
}

我会很感激的。

最后,DTO 列表应包含按小时和分钟分组的所有日期的总和(EHourQuarter 的 value 属性)。

这是一个例子。

注意:一张地图可以包含多个日期,目的是对所有的进行分组/求和。

鉴于这张地图:

{
   "2020-06-26":{
      "0":{
         "FOURTH":0.0,
         "FIRST":0.0,
         "THIRD":0.0,
         "SECOND":0.0
      },
      "1":{
         "FOURTH":0.0,
         "FIRST":0.0,
         "THIRD":0.0,
         "SECOND":0.0
      },
      "2":{
         "FOURTH":0.0,
         "FIRST":0.0,
         "THIRD":0.0,
         "SECOND":0.0
      },
      "3":{
         "FOURTH":0.0,
         "FIRST":0.0,
         "THIRD":0.0,
         "SECOND":0.0
      },
      "4":{
         "FOURTH":0.0,
         "FIRST":0.0,
         "THIRD":0.0,
         "SECOND":0.0
      },
      "5":{
         "FOURTH":0.0,
         "FIRST":0.0,
         "THIRD":0.0,
         "SECOND":0.0
      },
      "6":{
         "FOURTH":0.0,
         "FIRST":0.0,
         "THIRD":0.0,
         "SECOND":0.0
      },
      "7":{
         "FOURTH":0.0,
         "FIRST":0.0,
         "THIRD":0.0,
         "SECOND":0.0
      },
      "8":{
         "FOURTH":0.0,
         "FIRST":0.0,
         "THIRD":0.0,
         "SECOND":0.0
      },
      "9":{
         "FOURTH":5.0,
         "FIRST":5.0,
         "THIRD":5.0,
         "SECOND":5.0
      },
      "10":{
         "FOURTH":5.0,
         "FIRST":5.0,
         "THIRD":5.0,
         "SECOND":5.0
      },
      "11":{
         "FOURTH":5.0,
         "FIRST":5.0,
         "THIRD":5.0,
         "SECOND":5.0
      },
      "12":{
         "FOURTH":5.0,
         "FIRST":5.0,
         "THIRD":5.0,
         "SECOND":5.0
      },
      "13":{
         "FOURTH":5.0,
         "FIRST":5.0,
         "THIRD":5.0,
         "SECOND":5.0
      },
      "14":{
         "FOURTH":5.0,
         "FIRST":5.0,
         "THIRD":5.0,
         "SECOND":5.0
      },
      "15":{
         "FOURTH":5.0,
         "FIRST":5.0,
         "THIRD":5.0,
         "SECOND":5.0
      },
      "16":{
         "FOURTH":5.0,
         "FIRST":5.0,
         "THIRD":5.0,
         "SECOND":5.0
      },
      "17":{
         "FOURTH":5.0,
         "FIRST":5.0,
         "THIRD":5.0,
         "SECOND":5.0
      },
      "18":{
         "FOURTH":0.0,
         "FIRST":5.0,
         "THIRD":0.0,
         "SECOND":0.0
      },
      "19":{
         "FOURTH":0.0,
         "FIRST":0.0,
         "THIRD":0.0,
         "SECOND":0.0
      },
      "20":{
         "FOURTH":0.0,
         "FIRST":0.0,
         "THIRD":0.0,
         "SECOND":0.0
      },
      "21":{
         "FOURTH":0.0,
         "FIRST":0.0,
         "THIRD":0.0,
         "SECOND":0.0
      },
      "22":{
         "FOURTH":0.0,
         "FIRST":0.0,
         "THIRD":0.0,
         "SECOND":0.0
      },
      "23":{
         "FOURTH":0.0,
         "FIRST":0.0,
         "THIRD":0.0,
         "SECOND":0.0
      }
   }
}

像这样的列表应该是答案:

[
   {
      "hour":0,
      "minute":15,
      "occupancy":0.0
   },
   {
      "hour":0,
      "minute":30,
      "occupancy":0.0
   },
   {
      "hour":0,
      "minute":45,
      "occupancy":0.0
   },
   {
      "hour":0,
      "minute":60,
      "occupancy":0.0
   },
   {
      "hour":1,
      "minute":15,
      "occupancy":0.0
   },
   {
      "hour":1,
      "minute":30,
      "occupancy":0.0
   },
   {
      "hour":1,
      "minute":45,
      "occupancy":0.0
   },
   {
      "hour":1,
      "minute":60,
      "occupancy":0.0
   },
   {
      "hour":2,
      "minute":15,
      "occupancy":0.0
   },
   {
      "hour":2,
      "minute":30,
      "occupancy":0.0
   },
   {
      "hour":2,
      "minute":45,
      "occupancy":0.0
   },
   {
      "hour":2,
      "minute":60,
      "occupancy":0.0
   },
   {
      "hour":3,
      "minute":15,
      "occupancy":0.0
   },
   {
      "hour":3,
      "minute":30,
      "occupancy":0.0
   },
   {
      "hour":3,
      "minute":45,
      "occupancy":0.0
   },
   {
      "hour":3,
      "minute":60,
      "occupancy":0.0
   },
   {
      "hour":4,
      "minute":15,
      "occupancy":0.0
   },
   {
      "hour":4,
      "minute":30,
      "occupancy":0.0
   },
   {
      "hour":4,
      "minute":45,
      "occupancy":0.0
   },
   {
      "hour":4,
      "minute":60,
      "occupancy":0.0
   },
   {
      "hour":5,
      "minute":15,
      "occupancy":0.0
   },
   {
      "hour":5,
      "minute":30,
      "occupancy":0.0
   },
   {
      "hour":5,
      "minute":45,
      "occupancy":0.0
   },
   {
      "hour":5,
      "minute":60,
      "occupancy":0.0
   },
   {
      "hour":6,
      "minute":15,
      "occupancy":0.0
   },
   {
      "hour":6,
      "minute":30,
      "occupancy":0.0
   },
   {
      "hour":6,
      "minute":45,
      "occupancy":0.0
   },
   {
      "hour":6,
      "minute":60,
      "occupancy":0.0
   },
   {
      "hour":7,
      "minute":15,
      "occupancy":0.0
   },
   {
      "hour":7,
      "minute":30,
      "occupancy":0.0
   },
   {
      "hour":7,
      "minute":45,
      "occupancy":0.0
   },
   {
      "hour":7,
      "minute":60,
      "occupancy":0.0
   },
   {
      "hour":8,
      "minute":15,
      "occupancy":0.0
   },
   {
      "hour":8,
      "minute":30,
      "occupancy":0.0
   },
   {
      "hour":8,
      "minute":45,
      "occupancy":0.0
   },
   {
      "hour":8,
      "minute":60,
      "occupancy":0.0
   },
   {
      "hour":9,
      "minute":15,
      "occupancy":5.0
   },
   {
      "hour":9,
      "minute":30,
      "occupancy":5.0
   },
   {
      "hour":9,
      "minute":45,
      "occupancy":5.0
   },
   {
      "hour":9,
      "minute":60,
      "occupancy":5.0
   },
   {
      "hour":10,
      "minute":15,
      "occupancy":5.0
   },
   {
      "hour":10,
      "minute":30,
      "occupancy":5.0
   },
   {
      "hour":10,
      "minute":45,
      "occupancy":5.0
   },
   {
      "hour":10,
      "minute":60,
      "occupancy":5.0
   },
   {
      "hour":11,
      "minute":15,
      "occupancy":5.0
   },
   {
      "hour":11,
      "minute":30,
      "occupancy":5.0
   },
   {
      "hour":11,
      "minute":45,
      "occupancy":5.0
   },
   {
      "hour":11,
      "minute":60,
      "occupancy":5.0
   },
   {
      "hour":12,
      "minute":15,
      "occupancy":5.0
   },
   {
      "hour":12,
      "minute":30,
      "occupancy":5.0
   },
   {
      "hour":12,
      "minute":45,
      "occupancy":5.0
   },
   {
      "hour":12,
      "minute":60,
      "occupancy":5.0
   },
   {
      "hour":13,
      "minute":15,
      "occupancy":5.0
   },
   {
      "hour":13,
      "minute":30,
      "occupancy":5.0
   },
   {
      "hour":13,
      "minute":45,
      "occupancy":5.0
   },
   {
      "hour":13,
      "minute":60,
      "occupancy":5.0
   },
   {
      "hour":14,
      "minute":15,
      "occupancy":5.0
   },
   {
      "hour":14,
      "minute":30,
      "occupancy":5.0
   },
   {
      "hour":14,
      "minute":45,
      "occupancy":5.0
   },
   {
      "hour":14,
      "minute":60,
      "occupancy":5.0
   },
   {
      "hour":15,
      "minute":15,
      "occupancy":5.0
   },
   {
      "hour":15,
      "minute":30,
      "occupancy":5.0
   },
   {
      "hour":15,
      "minute":45,
      "occupancy":5.0
   },
   {
      "hour":15,
      "minute":60,
      "occupancy":5.0
   },
   {
      "hour":16,
      "minute":15,
      "occupancy":5.0
   },
   {
      "hour":16,
      "minute":30,
      "occupancy":5.0
   },
   {
      "hour":16,
      "minute":45,
      "occupancy":5.0
   },
   {
      "hour":16,
      "minute":60,
      "occupancy":5.0
   },
   {
      "hour":17,
      "minute":15,
      "occupancy":5.0
   },
   {
      "hour":17,
      "minute":30,
      "occupancy":5.0
   },
   {
      "hour":17,
      "minute":45,
      "occupancy":5.0
   },
   {
      "hour":17,
      "minute":60,
      "occupancy":5.0
   },
   {
      "hour":18,
      "minute":15,
      "occupancy":5.0
   },
   {
      "hour":18,
      "minute":30,
      "occupancy":0.0
   },
   {
      "hour":18,
      "minute":45,
      "occupancy":0.0
   },
   {
      "hour":18,
      "minute":60,
      "occupancy":0.0
   },
   {
      "hour":19,
      "minute":15,
      "occupancy":0.0
   },
   {
      "hour":19,
      "minute":30,
      "occupancy":0.0
   },
   {
      "hour":19,
      "minute":45,
      "occupancy":0.0
   },
   {
      "hour":19,
      "minute":60,
      "occupancy":0.0
   },
   {
      "hour":20,
      "minute":15,
      "occupancy":0.0
   },
   {
      "hour":20,
      "minute":30,
      "occupancy":0.0
   },
   {
      "hour":20,
      "minute":45,
      "occupancy":0.0
   },
   {
      "hour":20,
      "minute":60,
      "occupancy":0.0
   },
   {
      "hour":21,
      "minute":15,
      "occupancy":0.0
   },
   {
      "hour":21,
      "minute":30,
      "occupancy":0.0
   },
   {
      "hour":21,
      "minute":45,
      "occupancy":0.0
   },
   {
      "hour":21,
      "minute":60,
      "occupancy":0.0
   },
   {
      "hour":22,
      "minute":15,
      "occupancy":0.0
   },
   {
      "hour":22,
      "minute":30,
      "occupancy":0.0
   },
   {
      "hour":22,
      "minute":45,
      "occupancy":0.0
   },
   {
      "hour":22,
      "minute":60,
      "occupancy":0.0
   },
   {
      "hour":23,
      "minute":15,
      "occupancy":0.0
   },
   {
      "hour":23,
      "minute":30,
      "occupancy":0.0
   },
   {
      "hour":23,
      "minute":45,
      "occupancy":0.0
   },
   {
      "hour":23,
      "minute":60,
      "occupancy":0.0
   }
]

标签: javajava-8java-streamcollectors

解决方案


第一组并按小时/季度对合计占用率

(避免嵌套flatMap,因为它降低了代码的可读性)

Map<Entry<Integer, Integer>, Double> groups
    = map.entrySet()
         .stream()
         // Flatten the outer map, since you don't care about the days
         .flatMap(de -> de.getValue().entrySet().stream())
         // Flatten the map by combining hour key and quarter key into a single one
         .flatMap(he -> he.getValue()
                          .entrySet()
                          .stream()
                          .map(qe -> new SimpleEntry<>(new SimpleEntry<>(he.getKey(), qe.getKey().getValue()), qe.getValue())))
         // Sum the occupancy per each hour/quarter pair
         .collect(groupingBy(Entry::getKey, summingDouble(Entry::getValue)));

然后将分组的条目映射到您的 DTO 对象中

List<QuarterlyOccupancyDTO> list =
    groups.entrySet()
          .stream()
          .map(e -> new QuarterlyOccupancyDTO(e.getKey().getKey(), e.getKey().getValue(), e.getValue()))
          .collect(toList());

另一种纯函数方法:

(它是纯粹的,但似乎不太可读,IMO)

Collection<QuarterlyOccupancyDTO> dtos =
    map.entrySet()
       .stream()
       // Flatten the outer map, since you don't care about the days
       .flatMap(de -> de.getValue().entrySet()
                        .stream())
       // Flatten the map by merging hour key and quarter key into a single one
       .flatMap(he -> he.getValue()
                        .entrySet()
                        .stream()
                        .map(qe -> new SimpleEntry<>(new SimpleEntry<>(he.getKey(), qe.getKey().getValue()),
                                                     qe.getValue())))
       // Map each entry into a DTO object and then reduce the occupancy per each hour/quarter pair
       .collect(
           groupingBy(Entry::getKey,
                      mapping(e -> new QuarterlyOccupancyDTO(e.getKey().getKey(), e.getKey().getValue(), e.getValue()),
                              reducing(new QuarterlyOccupancyDTO(0, 0, 0.0),
                                       (a, b) -> new QuarterlyOccupancyDTO(b.getHour(), b.getMinute(), a.getOccupancy() + b.getOccupancy())))))
       .values();

推荐阅读