如何使用 Java 8 流 api 从映射列表创建映射映射

2022-09-03 13:38:48

背景

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

[
  {
    "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()

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


答案 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);
});

答案 2

仅使用工具(类似于以下内容)即可实现此目的:Stream

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)
        )
    ));

推荐