如何将来自父地图的地图值与 java 8 流相结合
How to combine Map values from parent Map with java 8 stream
我在地图中有一张地图,如下所示:
Map<String, Map<Integer, BigDecimal>> mapInMap; //--> with values like:
/*
"a": (1: BigDecimal.ONE),
"b": (2: BigDecimal.TEN),
"c": (1: BigDecimal.ZERO)
*/
我想通过预期以下结果来组合内部映射:
Map<Integer, BigDecimal> innerMapCombined; //--> with values:
/*
1: BigDecimal.ZERO,
2: BigDecimal.TEN
*/
这是我预定义组合映射并使用 forEach 的解决方案:
Map<Integer, BigDecimal> combined = new HashMap<>();
mapInMap.forEach((str, innerMap) -> {
innerMap.forEach(combined::putIfAbsent);
});
但这会忽略 (1: BigDecimal.ZERO)
。
你能提供一个 java 8 流的 1 行解决方案吗?
您的问题在于,一旦您初始化地图,并在内部地图上添加 重复键 ,您将重写这些键,因为 那些地图不接受重复的键。因此,你需要先改变这个:
Map<String, Map<Integer, BigDecimal>> mapInMap;
到Map
,允许重复键,例如Multimap 来自Google 番石榴:
Map<String, Multimap<Integer, BigDecimal>> mapInMap = new HashMap<>();
内部地图是这样创建的:
Multimap<Integer, BigDecimal> x1 = ArrayListMultimap.create();
x1.put(1, BigDecimal.ONE);
mapInMap.put("a", x1);
只有 现在您可以尝试使用 Java 8 Streams API 来解决您的问题。例如:
Map<Integer, BigDecimal> map = multiMap.values()
.stream()
.flatMap(map -> map.entries().stream())
.collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue,
(v1, v2) -> v2));
使用toMap
方法的mergeFunction参数解决重复键冲突。我们明确表示在重复的情况下取第二个值 (v1, v2) -> v2
。
问题:
要解决您当前的解决方案不起作用的原因,是因为 Map#putIfAbsent
方法仅在地图中添加并且不替换地图中已经存在的值。
使用for-each的解决方案:
Map#put
是一种方法,但它的局限性在于您无法决定是始终保留此类键的第一个值、计算一个新值还是始终使用最后一个值。出于这个原因,我建议使用 Map#computeIfPresent
和 Map#putIfAbsent
的组合,或者更好的方法,即 Map#merge(K, V, BiFunction)
和 BiFunction remappingFunction
:
remappingFunction - the function to recompute a value if present
Map<Integer, BigDecimal> resultMap = new HashMap<>();
for (Map<Integer, BigDecimal> map: mapInMap.values()) {
for (Map.Entry<Integer, BigDecimal> entry: map.entrySet()) {
resultMap.merge(entry.getKey(), entry.getValue(), (l, r) -> r);
}
}
使用流的解决方案API:
要在类似 Stream 的解决方案中重写它,方法是相同的。唯一的区别是 Stream 的声明语法API,但是,思想是非常相似的。
只需 flatMap 结构并使用 Collector.toMap(Function, Function, BinaryOperator
收集到映射,使用 BinaryOperator mergeFunction
合并重复键。
mergeFunction - a merge function, used to resolve collisions between values associated with the same key, as supplied to Map.merge(Object, Object, BiFunction)
Map<Integer, BigDecimal> resultMap = mapInMap.values().stream()
.flatMap(entries -> entries.entrySet().stream())
.collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (l, r) -> r));
注:@dreamcrash also deserves a credit for his good Stream API 速度方面。
结果:
{1=1, 2=10}
是你打印出这样的图的结果(注意BigDecimal
是打印成数字的)。此输出符合您的预期输出。
1=BigDecimal.ZERO
2=BigDecimal.TEN
请注意 Map#merge(K, V, BiFunction)
和 Collector.toMap(Function, Function, BinaryOperator
之间的相似之处,它们使用非常相似的方法获得相同的结果。
我在地图中有一张地图,如下所示:
Map<String, Map<Integer, BigDecimal>> mapInMap; //--> with values like:
/*
"a": (1: BigDecimal.ONE),
"b": (2: BigDecimal.TEN),
"c": (1: BigDecimal.ZERO)
*/
我想通过预期以下结果来组合内部映射:
Map<Integer, BigDecimal> innerMapCombined; //--> with values:
/*
1: BigDecimal.ZERO,
2: BigDecimal.TEN
*/
这是我预定义组合映射并使用 forEach 的解决方案:
Map<Integer, BigDecimal> combined = new HashMap<>();
mapInMap.forEach((str, innerMap) -> {
innerMap.forEach(combined::putIfAbsent);
});
但这会忽略 (1: BigDecimal.ZERO)
。
你能提供一个 java 8 流的 1 行解决方案吗?
您的问题在于,一旦您初始化地图,并在内部地图上添加 重复键 ,您将重写这些键,因为 那些地图不接受重复的键。因此,你需要先改变这个:
Map<String, Map<Integer, BigDecimal>> mapInMap;
到Map
,允许重复键,例如Multimap 来自Google 番石榴:
Map<String, Multimap<Integer, BigDecimal>> mapInMap = new HashMap<>();
内部地图是这样创建的:
Multimap<Integer, BigDecimal> x1 = ArrayListMultimap.create();
x1.put(1, BigDecimal.ONE);
mapInMap.put("a", x1);
只有 现在您可以尝试使用 Java 8 Streams API 来解决您的问题。例如:
Map<Integer, BigDecimal> map = multiMap.values()
.stream()
.flatMap(map -> map.entries().stream())
.collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue,
(v1, v2) -> v2));
使用toMap
方法的mergeFunction参数解决重复键冲突。我们明确表示在重复的情况下取第二个值 (v1, v2) -> v2
。
问题:
要解决您当前的解决方案不起作用的原因,是因为 Map#putIfAbsent
方法仅在地图中添加并且不替换地图中已经存在的值。
使用for-each的解决方案:
Map#put
是一种方法,但它的局限性在于您无法决定是始终保留此类键的第一个值、计算一个新值还是始终使用最后一个值。出于这个原因,我建议使用 Map#computeIfPresent
和 Map#putIfAbsent
的组合,或者更好的方法,即 Map#merge(K, V, BiFunction)
和 BiFunction remappingFunction
:
remappingFunction - the function to recompute a value if present
Map<Integer, BigDecimal> resultMap = new HashMap<>();
for (Map<Integer, BigDecimal> map: mapInMap.values()) {
for (Map.Entry<Integer, BigDecimal> entry: map.entrySet()) {
resultMap.merge(entry.getKey(), entry.getValue(), (l, r) -> r);
}
}
使用流的解决方案API:
要在类似 Stream 的解决方案中重写它,方法是相同的。唯一的区别是 Stream 的声明语法API,但是,思想是非常相似的。
只需 flatMap 结构并使用 Collector.toMap(Function, Function, BinaryOperator
收集到映射,使用 BinaryOperator mergeFunction
合并重复键。
mergeFunction - a merge function, used to resolve collisions between values associated with the same key, as supplied to Map.merge(Object, Object, BiFunction)
Map<Integer, BigDecimal> resultMap = mapInMap.values().stream()
.flatMap(entries -> entries.entrySet().stream())
.collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (l, r) -> r));
注:@dreamcrash also deserves a credit for his good Stream API
结果:
{1=1, 2=10}
是你打印出这样的图的结果(注意BigDecimal
是打印成数字的)。此输出符合您的预期输出。
1=BigDecimal.ZERO
2=BigDecimal.TEN
请注意 Map#merge(K, V, BiFunction)
和 Collector.toMap(Function, Function, BinaryOperator
之间的相似之处,它们使用非常相似的方法获得相同的结果。