【问题标题】:Java 8 stream grouping a List<Map<>> by the same <Key, Value> to a new List<Map<>>Java 8 流将 List<Map<>> 按相同的 <Key, Value> 分组到新的 List<Map<>>
【发布时间】:2020-10-04 23:08:43
【问题描述】:

我有一个List&lt;Map&lt;String,String&gt;&gt; 如:

Map<String, String> m1 = new HashMap<>();
m1.put("date", "2020.1.5");
m1.put("B", "10");

Map<String, String> m2 = new HashMap<>();
m2.put("date", "2020.1.5");
m2.put("A", "20");

Map<String, String> m3 = new HashMap<>();
m3.put("date", "2020.1.6");
m3.put("A", "30");

Map<String, String> m4 = new HashMap<>();
m4.put("date", "2020.1.7");
m4.put("C", "30");

List<Map<String, String>> before = new ArrayList<>();
before.add(m1);
before.add(m2);
before.add(m3);
before.add(m4);

我期望的结果是生成一个新的 List 映射,它按 date 分组,并且在同一 date 中设置的所有条目将放在一起,例如:

[{"A":"20","B":"10","date":"2020.1.5"},{"A":"30","date":"2020.1.6"},{"C":"30","date":"2020.1.7"}]

我尝试了以下方法,但始终不是我期望的结果。

stream().flatmap().collect(Collectors.groupingBy())

对此问题的一些附加评论:

我使用 for LOOP 解决了这个问题,但是当列表大小约为 50000 时应用程序挂起,因此我寻求一种性能更好的方法来执行此操作。据我所知,Java 8 流平面图可能是一种方式。 因此,关键点不仅在于重新映射,还在于以最高效的方式来实现这一点。

【问题讨论】:

  • 我对您的预期结果有疑问,您真的希望日期键和日期值与您的值在同一个映射中吗?为什么你不能有一个Map&lt;String, List&lt;Map&lt;String, String&gt;&gt;,其中第一张地图的键是日期,而List&lt;Map&lt;&gt;&gt; 是你的实际值?
  • 我还要问,为什么你需要用 java 8 流来做这个?我怀疑它的性能会更好,而且您肯定可以通过手动滚动代码的步骤以更易读的方式做到这一点。
  • @BeUndead 不确定为什么您认为 Java 8 解决方案不可读。也许不是那么高效。但绝对不是丑陋的代码。
  • @scigs,其实列表确实是我期望的回报,而不是地图
  • @SilviuBurcea,已经这样做了。我刚从一个严重的问题中回来。抱歉回复晚了。

标签: java java-8 java-stream collectors groupingby


【解决方案1】:
before
  .stream()
  .collect(Collectors.toMap((m) -> m.get("date"), m -> m, (a,b) -> {
      Map<String, String> res = new HashMap<>();
      res.putAll(a);
      res.putAll(b);
      return res;
  }))
  .values();

这就是您正在寻找的解决方案。

toMap 函数接收 3 个参数:

  • 键映射器,在您的情况下是日期
  • 值映射器,即正在处理的映射本身
  • merge 函数,它采用 2 个具有相同日期的地图并将所有键放在一起

输出:

[{date=2020.1.5, A=20, B=10}, {date=2020.1.6, A=30}, {date=2020.1.7, C=30}]

【讨论】:

    【解决方案2】:

    您可以使用groupingByCollector.of 实现这种方式

    List<Map<String, String>> list = new ArrayList<>(before.stream()
            .collect(Collectors.groupingBy(
                    k -> k.get("date"),
                    Collector.of( HashMap<String,String>::new,
                            (m,e)-> m.putAll(e),
                            (map1,map2)->{ map1.putAll(map2); return map1;}
                    ))).values());
    

    在这里,首先使用Collectors.groupingBy 按日期分组。然后使用Collector.of 定义自定义收集器,将List&lt;Map&lt;String, String&gt;&gt; 收集到Map&lt;String, String&gt;。使用地图值创建列表后。

    并使用 Java 9

    中的Collectors.flatMapping
    List<Map<String, String>> list = new ArrayList<>(before.stream()
            .collect(Collectors.groupingBy(
                    k -> k.get("date"),
                    Collectors.flatMapping(m -> m.entrySet().stream(), 
                        Collectors.toMap(k -> k.getKey(), v -> v.getValue(), (a,b) -> a))))
                   .values());
    

    【讨论】:

    • 非常好的答案!
    【解决方案3】:

    您可以使用一定数量的收集器,有序地实现相同的结果:

    • Collectors.groupingBy 按日期分组
    • Collectors.reducing 合并 Map&lt;String, String&gt; 项目
    • Collectors.collectingAndThen 将值从Map&lt;String, Optional&lt;Map&lt;String, String&gt;&gt;&gt; 转换为先前减少到最终输出List&lt;Map&lt;String, String&gt;&gt; 的结果。
    List<Map<String, String>> list = before.stream()
        .collect(Collectors.collectingAndThen(
            Collectors.groupingBy(
                m -> m.get("date"),
                Collectors.reducing((l, r) -> {
                    l.putAll(r);
                    return l; })
            ),
            o -> o.values().stream()
                           .flatMap(Optional::stream)
                           .collect(Collectors.toList())));
    
    

    list 包含您要查找的内容:

    [{date=2020.1.5, A=20, B=10}, {date=2020.1.6, A=30}, {date=2020.1.7, C=30}]

    重要提示:此解决方案有两个缺点:

    • 它看起来很笨拙,对于独立的观众来说可能不太清楚
    • 它会改变(修改)List&lt;Map&lt;String, String&gt;&gt; before 中包含的原始地图。

    【讨论】:

    • 警告:此解决方案会改变原始地图!
    • 我已经包含了注释。谢谢。
    【解决方案4】:

    可以这样做:

    List<Map<String, String>> remapped = before.stream()
        .collect(Collectors.groupingBy(m -> m.get("date")))
        .values().stream()
        .map(e -> e.stream()
                   .flatMap(m -> m.entrySet().stream())
                   .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (x1, x2) -> x1)))
        .collect(Collectors.toList());
    
    remapped.forEach(System.out::println);
    

    输出:

    {date=2020.1.5, A=20, B=10}
    {date=2020.1.6, A=30}
    {date=2020.1.7, C=30}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-08
      • 2017-01-28
      • 2016-11-02
      • 2014-02-09
      • 2021-04-29
      • 1970-01-01
      相关资源
      最近更新 更多