【问题标题】:How to create a map of maps from a list of maps with Java 8 streaming api如何使用 Java 8 流式 API 从地图列表中创建地图地图
【发布时间】:2018-10-04 16:35:33
【问题描述】:

背景

我有一个看起来像这样的地图列表:

[
  {
    "name": "A",
    "old": 0.25,
    "new": 0.3
  },
  {
    "name": "B",
    "old": 0.3,
    "new": 0.35
  },
  {
    "name": "A",
    "old": 0.75,
    "new": 0.7
  },
  {
    "name": "B",
    "old": 0.7,
    "new": 0.60
  }
]

我希望输出如下所示:

{
  "A": {
    "old": 1,
    "new": 1
  },
  "B": {
    "old": 1,
    "new": 0.95
  }
}

...oldnew 的值是每个相关条目的总和。

地图列表的数据类型是List<Map<String, Object>>,所以输出应该是Map<String, Map<String, Double>>

我的尝试

通过一些图表绘制、文档阅读和反复试验,我能够想出这个:

data.stream()
    .collect(
        Collectors.groupingBy(entry -> entry.get("name"),
            Collectors.summingDouble(entry ->
                Double.parseDouble(entry.get("old").toString())))
    );

产生一个Map<String, Double>类型的对象,输出是

{
  "A": 1,
  "B": 1
}

对于old 值的总和。但是,我无法将其完全转换为地图地图。像这样的:

data.stream()
    .collect(
        Collectors.groupingBy(entry -> entry.get("name"),
            Collectors.mapping(
                Collectors.groupingBy(entry -> entry.get("old"),
                    Collectors.summingDouble(entry ->
                        Double.parseDouble(entry.get("old").toString())
                    )
                ),
                Collectors.groupingBy(entry -> entry.get("new"),
                    Collectors.summingDouble(entry ->
                        Double.parseDouble(entry.get("new").toString())
                    )
                )
            )
        )
    );

不起作用,因为Collectors.mapping() 只需要一个映射函数和一个下游收集器,但我不确定如何一次映射两个值。

我需要另一个函数来创建两个不同值的映射吗?任何关于更好的方法的建议也非常感谢。

【问题讨论】:

  • name,oldnew 封装在一个单独的类中可以简化您的工作,您是否可以提取它?

标签: java lambda java-8 java-stream


【解决方案1】:

您可以使用流,但也可以使用MapcomputeIfAbsentmerge 方法:

Map<String, Map<String, Double>> result = new LinkedHashMap<>();
data.forEach(entry -> {
    String name = (String) entry.get("name");
    Map<String, Double> map = result.computeIfAbsent(name, k -> new HashMap<>());
    map.merge("old", (Double) entry.get("old"), Double::sum);
    map.merge("new", (Double) entry.get("new"), Double::sum);
});

【讨论】:

  • 这种方式肯定更具可读性。顺便说一句,甚至可以使用 toMap 收集器,但如果不进行重构,则不那么可读。
【解决方案2】:

只有使用Stream 工具(类似于this)才能实现这一点:

Map<String, Map<String, Double>> collect = data.stream().collect(
    Collectors.groupingBy(m -> (String)m.get("name"),
    Collector.of(LinkedHashMap::new,
        (acc, e) -> Stream.of("old", "new").forEach(key -> acc.merge(key, (Double) e.get(key), Double::sum)),
        (m1, m2) -> {
          m2.forEach((k, v) -> m1.merge(k, v, Double::sum));
          return m1;
        })
    ));

还有> Java 8方式:

Map<String, Map<String, Double>> stats = data.stream().collect(
    Collectors.groupingBy(m -> (String) m.get("name"),
        Collectors.flatMapping(m -> m.entrySet().stream().filter(e -> !"name".equals(e.getKey())),
            Collectors.toMap(Map.Entry::getKey, e -> (Double)e.getValue(), Double::sum, LinkedHashMap::new)
        )
    ));

【讨论】:

  • 这个其实还不错,1+
【解决方案3】:

第一次尝试时您已接近解决方案,但是您需要执行一些自定义代码才能完全完成图片。

您将需要实现自己的collector,将多个地图转换为单个双地图。

这看起来像:

       Collector.of(
            () -> new HashMap<>(),
            (Map<String, Double>target, Map<String, Object> source) -> {
                target.merge("old", (Double)source.get("old"), Double::sum);
                target.merge("new", (Double)source.get("new"), Double::sum);
            },
            (Map<String, Double> map1, Map<String, Double> map2) -> {
                map2.forEach((k, v) -> map1.merge(k, v, Double::sum));
                return map1;
            }
        ) 

这结合你的初始分组尝试解决了图片:

data.stream()
    .collect(
        Collectors.groupingBy(entry -> entry.get("name"),
            // Insert collector here
        )
    );

在线完整代码示例:http://tpcg.io/pJftrJ

【讨论】:

    【解决方案4】:

    您可以声明一个名为Pair的类

    public class Pair {
        private final double oldVal;
        private final double newVal;
    
        public Pair(double oldVal, double newVal) {
            super();
            this.oldVal = oldVal;
            this.newVal = newVal;
        }
    
        public double getOldVal() {
            return oldVal;
        }
    
        public double getNewVal() {
            return newVal;
        }
    
        @Override
        public String toString() {
            return "{oldVal=" + oldVal + ", newVal=" + newVal + "}";
        }
    
    }
    

    那就这样吧,

    Map<Object, Pair> result = sourceMaps.stream()
            .collect(Collectors.toMap(m -> m.get("name"),
                    m -> new Pair((double) m.get("old"), (double) m.get("new")),
                    (p1, p2) -> new Pair(p1.getOldVal() + p2.getOldVal(), p1.getNewVal() + p2.getNewVal())));
    

    这是输出,

    {A={oldVal=1.0, newVal=1.0}, B={oldVal=1.0, newVal=0.95}}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-04
      • 1970-01-01
      • 1970-01-01
      • 2021-03-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多