如何将具有相同键但不同值的多个地图合并为一个地图
How to merge multiple Maps having the same keys but different values into a single Map
我有 n
地图 那种:
HashMap<String,Double> map1;
HashMap<String,Double> map2; ...
我该怎么做才能将所有这些 maps 合并成一个 single map?
我想在不丢失数据的情况下实现这一点。
示例:
map1 has entries: {<X1, Y1>, <X2,Y2>, <X3,Y3>}
map2 has entries: {<X1, Y6>, <X2, Y7>, <X3,Y8>}
mergedMap: {<X1, Y1+Y6>, <X2,Y2+Y7>, <X3, Y3+Y8>}
我试过这个:
ArrayList<HashMap<String, Double>> listHashMap = new ArrayList<>();
HashMap<String,Double> mergedMap = new HashMap<>();
for(HashMap<String,Double> map: listHashMap) {
mergedMap.putAll(map);
}
但我了解到映射到同一键的值将被替换 (put
),而不是添加。
我该怎么做才能将映射到同一个键的每个值加起来?
一种选择是使用 Map.compute
方法。它的第二个参数是一个重映射函数,可以像下面的例子一样使用:
Map<String, Double> inputMap1 = Map.of("X1", 5.0, "X2", 3.0);
Map<String, Double> inputMap2 = Map.of("X1", 7.0, "X3", 9.0);
List<Map<String, Double>> listOfMaps = List.of(inputMap1, inputMap2);
Map<String, Double> mergedMap = new HashMap<>();
for (Map<String, Double> map : listOfMaps) {
map.forEach((key, value) -> {
mergedMap.compute(key, (k, oldValue) -> oldValue == null ? value : oldValue + value);
});
}
System.out.println(mergedMap);
如果 mergedMap
中还没有带有 key
的条目,oldValue
是 null
,否则是旧值。
这会打印:
{X1=12.0, X2=3.0, X3=9.0}
更新:我可能更喜欢 。我会将此答案作为有趣或有教育意义的替代方案发布。
你的示例数据有缺陷(重复键),所以我制作了另一组数据。
Map < String, Double > map1 =
Map.of(
"X1" , 1d ,
"X2" , 1d
);
Map < String, Double > map2 =
Map.of(
"X1" , 2d ,
"X2" , 2d ,
"X3" , 7d
);
定义一个新的 Map
来保存结果。
Map < String, Double > results = new HashMap <>();
Stream
& Map#getOrDefault
通过制作每个流的流来处理两个输入映射,将这些流连接成一个流。对于该流中的每个映射条目,将键放入新映射中,值为 either 条目的值在第一次出现时或将条目的值添加到先前放置的值。
Stream
.concat( map1.entrySet().stream() , map2.entrySet().stream() )
.forEach(
stringDoubleEntry ->
results.put(
stringDoubleEntry.getKey() , // key
results.getOrDefault( stringDoubleEntry.getKey() , 0d ) + stringDoubleEntry.getValue() ) // value
);
System.out.println( "results.toString() = " + results );
看到这个code run live at Ideone.com。
map = {X1=3.0, X2=3.0, X3=7.0}
没有流
如果您还不熟悉流,您可以使用一对 for-each 循环来处理两个输入映射中的每一个。
处理第一张地图。
for ( Map.Entry < String, Double > stringDoubleEntry : map1.entrySet() )
{
String k = stringDoubleEntry.getKey();
Double v =
results
.getOrDefault( stringDoubleEntry.getKey() , 0d ) // Returns either (A) a value already put into `results` map, or else (B) a default value of zero.
+ stringDoubleEntry.getValue();
results.put( k , v ); // Replaces any existing entry (key-value pair) with this pair.
}
对第二个输入地图做同样的事情。这里唯一的区别是第一行,map1.entrySet()
变成了 map2.entrySet()
.
for ( Map.Entry < String, Double > stringDoubleEntry : map2.entrySet() )
{
String k = stringDoubleEntry.getKey();
Double v =
results
.getOrDefault( stringDoubleEntry.getKey() , 0d ) // Returns either (A) a value already put into `results` map, or else (B) a default value of zero.
+ stringDoubleEntry.getValue();
results.put( k , v ); // Replaces any existing entry (key-value pair) with this pair.
}
使用流你可以做:
ArrayList<HashMap<String, Double>> listHashMaps = // your list of maps
Map<String,Double> mergedMap =
listHashMaps.stream()
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Double::sum));
你可以利用Java8种方法merge()
来合并每个条目的数据:
List<Map<String, Double>> list = // initializing source list
Map<String, Double> mergedMap = new HashMap<>();
list.forEach(map -> map.forEach((k, v) -> mergedMap.merge(k, v, Double::sum)));
看到code run live at Ideone.com。
或者您可以将 Stream API 与 built-in 收集器组合使用 groupingBy()
and summingDouble()
:
Map<String, Double> mergedMap = list.stream()
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.groupingBy(
Map.Entry::getKey, Collectors.summingDouble(Map.Entry::getValue)
));
查看 Oracle 提供的这些教程,了解有关 lambda expressions and streams的更多信息。
正如其他人在评论中指出的那样,一个 Map
不能有多个具有相同值的键,因此我为每个键关联了一个 List
双精度值以使其尽可能接近尽可能以你的例子为例。
为了实现您的目标,您可以流式传输 Maps
的条目并使用 collect(Collectors.toMap())
收集它们。键将保持不变, Lists
的内容将被求和,当键发生碰撞时,您可以简单地将第一次和第二次碰撞 List
.
获得的双精度值相加
Map<String, List<Double>> map1 = Map.of("X1", new ArrayList<>(List.of(1.0, 3.0, 5.0)), "X2", new ArrayList<>(List.of(2.0, 4.0)));
Map<String, List<Double>> map2 = Map.of("X4", new ArrayList<>(List.of(10.0, 7.0)), "X1", new ArrayList<>(List.of(12.0)));
Map<String, Double> mapRes = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream())
.collect(Collectors.toMap(entry -> entry.getKey(),
entry -> entry.getValue().stream().collect(Collectors.summingDouble(Double::doubleValue)),
(val1, val2) -> val1 + val2));
这是一种“老狗学不会新把戏”的方法:
public static Map<String, Double> addMapValues
(Collection<Map<String, Double>> mapsIn) {
Map<String, Double> sums = new HashMap<> ();
for (Map<String, Double> aMap : mapsIn) {
for (Map.Entry<String, Double> entry: aMap.entrySet()) {
Double runningTotal = sums.get(entry.getKey());
if (runningTotal == null) {
sums.put(entry.getKey(), entry.getValue());
} else {
runningTotal += entry.getValue();
sums.put(entry.getKey(), runningTotal);
}
}
}
return sums;
}
我有 n
地图 那种:
HashMap<String,Double> map1;
HashMap<String,Double> map2; ...
我该怎么做才能将所有这些 maps 合并成一个 single map?
我想在不丢失数据的情况下实现这一点。
示例:
map1 has entries: {<X1, Y1>, <X2,Y2>, <X3,Y3>}
map2 has entries: {<X1, Y6>, <X2, Y7>, <X3,Y8>}
mergedMap: {<X1, Y1+Y6>, <X2,Y2+Y7>, <X3, Y3+Y8>}
我试过这个:
ArrayList<HashMap<String, Double>> listHashMap = new ArrayList<>();
HashMap<String,Double> mergedMap = new HashMap<>();
for(HashMap<String,Double> map: listHashMap) {
mergedMap.putAll(map);
}
但我了解到映射到同一键的值将被替换 (put
),而不是添加。
我该怎么做才能将映射到同一个键的每个值加起来?
一种选择是使用 Map.compute
方法。它的第二个参数是一个重映射函数,可以像下面的例子一样使用:
Map<String, Double> inputMap1 = Map.of("X1", 5.0, "X2", 3.0);
Map<String, Double> inputMap2 = Map.of("X1", 7.0, "X3", 9.0);
List<Map<String, Double>> listOfMaps = List.of(inputMap1, inputMap2);
Map<String, Double> mergedMap = new HashMap<>();
for (Map<String, Double> map : listOfMaps) {
map.forEach((key, value) -> {
mergedMap.compute(key, (k, oldValue) -> oldValue == null ? value : oldValue + value);
});
}
System.out.println(mergedMap);
如果 mergedMap
中还没有带有 key
的条目,oldValue
是 null
,否则是旧值。
这会打印:
{X1=12.0, X2=3.0, X3=9.0}
更新:我可能更喜欢
你的示例数据有缺陷(重复键),所以我制作了另一组数据。
Map < String, Double > map1 =
Map.of(
"X1" , 1d ,
"X2" , 1d
);
Map < String, Double > map2 =
Map.of(
"X1" , 2d ,
"X2" , 2d ,
"X3" , 7d
);
定义一个新的 Map
来保存结果。
Map < String, Double > results = new HashMap <>();
Stream
& Map#getOrDefault
通过制作每个流的流来处理两个输入映射,将这些流连接成一个流。对于该流中的每个映射条目,将键放入新映射中,值为 either 条目的值在第一次出现时或将条目的值添加到先前放置的值。
Stream
.concat( map1.entrySet().stream() , map2.entrySet().stream() )
.forEach(
stringDoubleEntry ->
results.put(
stringDoubleEntry.getKey() , // key
results.getOrDefault( stringDoubleEntry.getKey() , 0d ) + stringDoubleEntry.getValue() ) // value
);
System.out.println( "results.toString() = " + results );
看到这个code run live at Ideone.com。
map = {X1=3.0, X2=3.0, X3=7.0}
没有流
如果您还不熟悉流,您可以使用一对 for-each 循环来处理两个输入映射中的每一个。
处理第一张地图。
for ( Map.Entry < String, Double > stringDoubleEntry : map1.entrySet() )
{
String k = stringDoubleEntry.getKey();
Double v =
results
.getOrDefault( stringDoubleEntry.getKey() , 0d ) // Returns either (A) a value already put into `results` map, or else (B) a default value of zero.
+ stringDoubleEntry.getValue();
results.put( k , v ); // Replaces any existing entry (key-value pair) with this pair.
}
对第二个输入地图做同样的事情。这里唯一的区别是第一行,map1.entrySet()
变成了 map2.entrySet()
.
for ( Map.Entry < String, Double > stringDoubleEntry : map2.entrySet() )
{
String k = stringDoubleEntry.getKey();
Double v =
results
.getOrDefault( stringDoubleEntry.getKey() , 0d ) // Returns either (A) a value already put into `results` map, or else (B) a default value of zero.
+ stringDoubleEntry.getValue();
results.put( k , v ); // Replaces any existing entry (key-value pair) with this pair.
}
使用流你可以做:
ArrayList<HashMap<String, Double>> listHashMaps = // your list of maps
Map<String,Double> mergedMap =
listHashMaps.stream()
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Double::sum));
你可以利用Java8种方法merge()
来合并每个条目的数据:
List<Map<String, Double>> list = // initializing source list
Map<String, Double> mergedMap = new HashMap<>();
list.forEach(map -> map.forEach((k, v) -> mergedMap.merge(k, v, Double::sum)));
看到code run live at Ideone.com。
或者您可以将 Stream API 与 built-in 收集器组合使用 groupingBy()
and summingDouble()
:
Map<String, Double> mergedMap = list.stream()
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.groupingBy(
Map.Entry::getKey, Collectors.summingDouble(Map.Entry::getValue)
));
查看 Oracle 提供的这些教程,了解有关 lambda expressions and streams的更多信息。
正如其他人在评论中指出的那样,一个 Map
不能有多个具有相同值的键,因此我为每个键关联了一个 List
双精度值以使其尽可能接近尽可能以你的例子为例。
为了实现您的目标,您可以流式传输 Maps
的条目并使用 collect(Collectors.toMap())
收集它们。键将保持不变, Lists
的内容将被求和,当键发生碰撞时,您可以简单地将第一次和第二次碰撞 List
.
Map<String, List<Double>> map1 = Map.of("X1", new ArrayList<>(List.of(1.0, 3.0, 5.0)), "X2", new ArrayList<>(List.of(2.0, 4.0)));
Map<String, List<Double>> map2 = Map.of("X4", new ArrayList<>(List.of(10.0, 7.0)), "X1", new ArrayList<>(List.of(12.0)));
Map<String, Double> mapRes = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream())
.collect(Collectors.toMap(entry -> entry.getKey(),
entry -> entry.getValue().stream().collect(Collectors.summingDouble(Double::doubleValue)),
(val1, val2) -> val1 + val2));
这是一种“老狗学不会新把戏”的方法:
public static Map<String, Double> addMapValues
(Collection<Map<String, Double>> mapsIn) {
Map<String, Double> sums = new HashMap<> ();
for (Map<String, Double> aMap : mapsIn) {
for (Map.Entry<String, Double> entry: aMap.entrySet()) {
Double runningTotal = sums.get(entry.getKey());
if (runningTotal == null) {
sums.put(entry.getKey(), entry.getValue());
} else {
runningTotal += entry.getValue();
sums.put(entry.getKey(), runningTotal);
}
}
}
return sums;
}