使用 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
值与最内层 Map
的 key
值相加。
此外,最外层的 Maps
每个 2nd-Level-Map
都有一个 Key-Value-Pair
,因此每个都将具有相同的 2nd-Level-Keys
。在3rd-Level-Keyset
s中可以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}}
我有以下结构:
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
值与最内层 Map
的 key
值相加。
此外,最外层的 Maps
每个 2nd-Level-Map
都有一个 Key-Value-Pair
,因此每个都将具有相同的 2nd-Level-Keys
。在3rd-Level-Keyset
s中可以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}}