使用 Java 流对内部映射进行分组

Grouping of inner Maps with Java Streams

我有以下结构:

Map<String,Map<String,Map<String,Integer>>>

现在我想忽略一级地图,根据二级地图的键对三级地图进行分组(并总结)。 澄清一些示例条目:

Entry 1: ["1"["A"[[a,1];[b,2]];"B"[[a,3];[c,1]]]]
Entry 2: ["2"["A"[[b,2];[c,1]];"B"[[a,5];[b,0]]]]

期望的输出:

Entry 1: ["A"[[a,1];[b,4];[c,1]]]
Entry 4: ["B"[[a,8];[b,0];[c,1]]]

所以要做到这一点,我首先根据我的二级键 ("A","B") 对我的 Entry-stream 进行分组,如果没有其他操作,最终得到的结构如下以下:

Map<String,List<Entry<String,Map<String,Integer>>>>

这就是我被困的地方。我如何从我的条目列表中获取我的 Map<String,Integer>(对于每个外部地图,具体来说)?

我假设的简单代码保证需要:

        initialMap.values().stream()
                            .flatMap(m -> m.entrySet().stream())
                            .collect(Collectors.groupingBy(Map.Entry::getKey));

总结:

如何将 Map<String,Map<String,Map<String,Integer>>> 转换为 Map<String<Map<String,Integer>>,忽略最外面的 Map,根据我的第二层 [=21] 对最里面的 Maps 进行分组=] 并将我的 Integer 值与最内层 Mapkey 值相加。 此外,最外层的 Maps 每个 2nd-Level-Map 都有一个 Key-Value-Pair,因此每个都将具有相同的 2nd-Level-Keys。在3rd-Level-Keysets中可以Keys在其他3rd-Level-Maps

中找不到
Map<String, Map<String, Integer>> result = 
    initialMap
        .values()
        .stream()
        .flatMap(m -> m.entrySet().stream())
        .collect(Collectors.groupingBy(Map.Entry::getKey, 
                                       Collectors.groupingBy(e -> mapToFirstEntry(e.getValue()).getKey(), 
                                                             Collectors.summingInt(e -> mapToFirstEntry(e.getValue()).getValue()))));

但它假定第三层 Map<String, Integer> 包含一个条目,并且有一种方法可以获取该条目:

public static Map.Entry<String, Integer> mapToFirstEntry(Map<String, Integer> map) {
    return map.entrySet().iterator().next();
} 

这里要记住一件事:流在概念上表示通过 "pipe" 排序的单个元素。流运行时总是单个元素,无论源总共备份了一个、多个还是无限个元素。

您在这里尝试做的是表示几个嵌套循环,按照以下行:

Map<String, Map<String, Integer>> result = new HashMap<>();
for (Map<String, Map<String, Integer>> firstMap : inputMap.values()) {
    for (Entry<String, Map<String, Integer>> firstEntry : firstMap.entrySet()) {
        String upperCaseKey = firstEntry.getKey();
        Map<String, Ingeter> resultEntry = result.computeIfAbsent(
            upperCaseKey,
            _k -> new HashMap<>());
        for (Entry<String, Integer> secondEntry : firstEntry.getValue().entrySet()) {

            resultEntry.merge(secondEntry.getKey(), secondEntry.getValue(), Integer::sum);

        }
    }
}

使用流的更好方法之一是通过Collector组合:

inputMap.values().stream()
    .flatMap(map -> map.entrySet().stream())

    .flatMap(firstEntry -> firstEntry.getValue()
                           .entrySet().stream()
                           .map(secondEntry -> new SimpleImmutableEntry(
                                                       firstEntry.getKey(),
                                                       secondEntry)
                           )
    )
    .collect(Collectors.groupingBy(
        Entry::getKey,
        Collectors.groupingBy(
          compositeEntry -> compositeEntry.getValue().getKey(),
          Collectors.summingInt(compositeEntry -> compositeEntry.getValue().getValue())
        )
    ));

一般来说,这应该可以解决问题,但请注意我必须首先构建一个复合条目,以将元素计数保持为 1,然后嵌套两个分组收集器。这就是为什么我认为像您这样的任务不适合 API。它也很可能需要您向编译器提供一些帮助,因为它可能很难正确推断所有类型。

另请注意,这不是唯一的方法:Stream API 非常灵活,您可能会看到更多其他方法来做同样的事情。

如果你有自由使用Java9,我建议你使用flatMapping收集器来解决这个问题。这种方法更具可读性,并且对我来说产生的视觉混乱更少。这是它的样子。

Map<String, Map<String, Integer>> summaryMap = map.values().stream()
    .flatMap(m -> m.entrySet().stream())
    .collect(Collectors.groupingBy(Map.Entry::getKey,
        Collectors.flatMapping(e -> e.getValue().entrySet().stream(),
            Collectors.groupingBy(Map.Entry::getKey, 
                Collectors.summingInt(Map.Entry::getValue)))));

该程序产生以下输出:

{A={a=1, b=4, c=1}, B={a=8, b=0, c=1}}