代码之家  ›  专栏  ›  技术社区  ›  homersimpson

如何使用Java8流api从地图列表创建地图地图

  •  15
  • homersimpson  · 技术社区  · 6 年前

    背景

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

    [
      {
        "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
      }
    }
    

    …价值观 old new 为每个相关条目求和。

    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
    }
    

    古老的

    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() 只需要一个映射函数和一个下游收集器,但我不知道如何同时映射两个值。

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

    4 回复  |  直到 6 年前
        1
  •  9
  •   Ousmane D.    6 年前

    Map computeIfAbsent merge 方法:

    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
  •  6
  •   Flown    6 年前

    只有使用 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)
            )
        ));
    
        3
  •  4
  •   Ferrybig    6 年前

    您的第一次尝试已经接近解决方案,但是您需要执行一些自定义代码来完全完成这幅图。

    你需要实现你自己的 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
  •  2
  •   Ravindra Ranwala    6 年前

    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}}