如何将多个Map的List合并为一个Map的Map?

7

你能帮我解决 Java Streams 的问题吗?

从标题可以看出,我需要将 List<Map<String, Map<String, Genuineness>>> 合并到 Map<String, Map<String, Genuineness>> 中。

该列表用 List<Map<String, Map<String, Genuineness>>> 表示,如下所示:

[  
   {  
      "USER_1":{  
         "APP_1":{  
            "total":1,
            "totalGenuine":1,
            "totalDevelopment":1
         }
      },
      "USER_2":{  
         "APP_1":{  
            "total":1,
            "totalGenuine":1,
            "totalDevelopment":1
         },
         "APP_2":{  
            "total":2,
            "totalGenuine":2,
            "totalDevelopment":2
         }
      }
   },
   {  
      "USER_1":{  
         "APP_1":{  
            "total":1,
            "totalGenuine":1,
            "totalDevelopment":1
         }
      },
      "USER_2":{  
         "APP_1":{  
            "total":1,
            "totalGenuine":1,
            "totalDevelopment":1
         },
         "APP_2":{  
            "total":2,
            "totalGenuine":2,
            "totalDevelopment":2
         }
      }
   }
]

因此,您可以看到,重复的键可能无处不在。 我的目标是通过合并 Genuineness 将它们组合成 Map<String,Map<String,Genuineness>>。合并 Genuineness 简单地意味着返回一个新对象,其中包含汇总值 totaltotalGenuinetotalDevelopment

这是我失败的实现:

final Map<String, Map<String, Genuineness>> map = taskHandles.stream().map(this::mapTaskHandle)
                .flatMap(m -> m.entrySet().stream()).collect(
                        Collectors.toMap(Map.Entry::getKey, e -> e.getValue().entrySet().stream()
                                .collect(
                                        Collectors.toMap(Map.Entry::getKey,
                                                g -> new Genuineness(g.getValue().getTotal(), g.getValue().getTotalGenuine(), g.getValue().getTotalDevelopment()),
                                                (g1, g2) -> new Genuineness(g1.getTotal() + g2.getTotal(),
                                                        g1.getTotalGenuine() + g2.getTotalGenuine(),
                                                        g1.getTotalDevelopment() + g2.getTotalGenuine()
                                                )
                                        )
                                )
                        )
                );

它会显示错误信息:
java.lang.IllegalStateException: Duplicate key {TEST_33_33_APP_1=live.attach.billing.domain.model.billing.Genuineness@951b6fe}

所以,在我的实现中,我指出了如何合并内部映射,但没有合并外部映射的值,我不知道该怎么做。

非常感谢您的帮助。提前谢谢!

更新: 期望输出:

   {  
      "USER_1":{  
         "APP_1":{  
            "total":2,
            "totalGenuine":2,
            "totalDevelopment":2
         }
      },
      "USER_2":{  
         "APP_1":{  
            "total":2,
            "totalGenuine":2,
            "totalDevelopment":2
         },
         "APP_2":{  
            "total":4,
            "totalGenuine":4,
            "totalDevelopment":4
         }
      }
   }

请提供期望的输出,否则几乎无法找出您要求什么。 - fps
考虑使用Java Streams的flatMap功能。它可以将分层集合打散为对象的平坦流。 - BrentR
嗨,谢谢,我已经添加了预期输出和简化输入数据。 - Pasha
我强烈建议将两个“真实性”对象的合并逻辑移动到“真实性”类中,例如添加一个“Genuineness merge(Genuineness other)”方法,该方法返回一个新的合并后的“Genuineness”对象。如果您需要添加另一个需要合并的“Genuineness”字段,则这将简化您的代码并使其更容易。基本上,让“真实性”负责知道如何合并两个(或多个)自身的实例,即将逻辑放在它应该在的地方。 - Andreas
这是一个好的设计模式吗? - Pasha
2个回答

4
说实话,这是一个非常难以处理的数据结构,代码维护者将很难找出出现的问题。你应该退一步考虑重构代码,无论如何,你都可以通过在最外层的toMap中使用以下合并函数来解决缺失的部分:
(l, r) -> {
      r.forEach((k, v) -> l.merge(k, v,
                    (bi, bii) -> new Genuineness(bi.getTotal() + bii.getTotal(),
                               bi.getTotalGenuine() + bii.getTotalGenuine(),
                               bi.getTotalDevelopment() + bii.getTotalGenuine())));
       return l;
}

完整代码:
taskHandles.stream().map(this::mapTaskHandle)
                .flatMap(m -> m.entrySet().stream()).collect(
                        Collectors.toMap(Map.Entry::getKey, e -> e.getValue().entrySet().stream()
                                .collect(
                                        Collectors.toMap(Map.Entry::getKey,
                                                g -> new Genuineness(g.getValue().getTotal(), g.getValue().getTotalGenuine(), g.getValue().getTotalDevelopment()),
                                                (g1, g2) -> new Genuineness(g1.getTotal() + g2.getTotal(),
                                                        g1.getTotalGenuine() + g2.getTotalGenuine(),
                                                        g1.getTotalDevelopment() + g2.getTotalGenuine()
                                                )
                                        )
                                ),(l, r) -> {
                                  r.forEach((k, v) -> l.merge(k, v,
                                          (bi, bii) -> new Genuineness(bi.getTotal() + bii.getTotal(),
                                                  bi.getTotalGenuine() + bii.getTotalGenuine(),
                                                  bi.getTotalDevelopment() + bii.getTotalGenuine())));
                                  return l;
                                }

                        )
                );

Ideone

:Ideone

(+1) 正在研究一个类似的解决方案,这个问题以前被 OP 在其中一个线程中称为 map-reduction。 - Naman

3
尽管Aomine提出的解决方案似乎是正确的,但您可以通过定义一个BinaryOperator<Genuineness>来提高代码可读性并简化它:
BinaryOperator<Genuineness> remappingGenuineness = (g1, g2) -> new Genuineness(g1.getTotal() + g2.getTotal(),
        g1.getTotalGenuine() + g2.getTotalGenuine(),
        g1.getTotalDevelopment() + g2.getTotalGenuine()
);

然后进一步将其用作:

final Map<String, Map<String, Genuineness>> map = taskHandles.stream()
        .flatMap(m -> m.entrySet().stream())
        .collect(Collectors.toMap(Map.Entry::getKey,
                e -> e.getValue().entrySet().stream().collect(
                        Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, remappingGenuineness)),
                (a, b) -> {
                    a.forEach((k, v) -> b.merge(k, v, remappingGenuineness));
                    return b;
                }));

是的,目前看起来是一个不错的改进,我并没有太多考虑重构 OP 的代码,因为解决问题本身就耗费了我很多精力。+1 - Ousmane D.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接