如何在Java中合并多个HashMap,并对相同键的值求和

18

我想要合并多个HashMap,并且对于相同的键值进行求和,以下是一个玩具示例来解释我的问题:

    HashMap<String, Integer> m = new HashMap<>();
    HashMap<String, Integer> m2 = new HashMap<>();

    m.put("apple", 2);
    m.put("pear", 3);
    m2.put("apple", 9);
    m2.put("banana", 6);

我尝试了putAll函数

m.putAll(m2);

输出如下:

{banana=6, apple=9, pear=3}

但对于这个问题,它的结果不正确。我想要输出:

{banana=6, apple=11, pear=3}

在Java中该怎样做呢?


我将所需的结果哈希映射更清晰地重写为{香蕉=6,苹果=9 + 2,梨=3}。如果多个哈希映射具有相同的键,则结果将是值的总和。如果其他哈希映射没有相同的键,则值不会改变,并且所有哈希映射中的所有键都将在结果哈希映射中以其值出现。 - Melike Ttkn
我喜欢每个后续答案变得越来越长,Java专家们表达了他们是相应先前版本的更好版本。这是一个很好的例子,可以开始学习该语言及其社区 :) - EugZol
8个回答

35

如果您正在使用Java 8,则可以使用Map的新merge方法。

m2.forEach((k, v) -> m.merge(k, v, (v1, v2) -> v1 + v2));

1
m 中的条目如果在 m2 中不存在怎么办? - sprinter
m 将包含合并的结果,代码输出为:{banana=6, apple=11, pear=3} - prunge
哦,我明白了——您是将内容合并到原始地图中而不是创建新地图。我当时没有意识到这是原帖的意图,但现在从他的问题中我看到了。所以它本质上依赖于原始地图的可变性。 - sprinter
这也可以写成 m2.forEach((k, v) -> m.merge(k, v, Integer::sum)); 或者 Long::sum 或者 Double::sum 或者 Float::sum,具体取决于你要对什么进行求和。 - Paul

12

这是Java 8流非常好的用例。您可以连接条目的流,然后将它们收集到一个新地图中:

Map<String, Integer> combinedMap = Stream.concat(m1.entrySet().stream(), m2.entrySet().stream())
    .collect(Collectors.groupingBy(Map.Entry::getKey,
             Collectors.summingInt(Map.Entry::getValue)));

这种解决方案有很多优点,包括可以并行处理、扩展到您想要的任意数量的地图并且可以轻松地筛选地图(如果需要)。它还不需要原始地图是可变的。


1

这个方法应该可以解决问题(在Java 5+中)

public static <K> Map<K, Integer> mergeAndAdd(Map<K, Integer>... maps) {
    Map<K, Integer> result = new HashMap<>();
    for (Map<K, Integer> map : maps) {
        for (Map.Entry<K, Integer> entry : map.entrySet()) {
            K key = entry.getKey();
            Integer current = result.get(key);
            result.put(key, current == null ? entry.getValue() : entry.getValue() + current);
        }
    }
    return result;
}

1
这是我的快速简单实现:

Here's my quick and dirty implementation:

import java.util.HashMap;
import java.util.Map;

public class MapMerger {

    public static void main(String[] args) {
        HashMap<String, Integer> m = new HashMap<>();
        HashMap<String, Integer> m2 = new HashMap<>();

        m.put("apple", 2);
        m.put("pear", 3);
        m2.put("apple", 9);
        m2.put("banana", 6);

        final Map<String, Integer> result = (new MapMerger()).mergeSumOfMaps(m, m2);
        System.out.println(result);
    }

    public Map<String, Integer> mergeSumOfMaps(Map<String, Integer>... maps) {
        final Map<String, Integer> resultMap = new HashMap<>();
        for (final Map<String, Integer> map : maps) {
            for (final String key : map.keySet()) {
                final int value;
                if (resultMap.containsKey(key)) {
                    final int existingValue = resultMap.get(key);
                    value = map.get(key) + existingValue;
                }
                else {
                    value = map.get(key);
                }
                resultMap.put(key, value);
            }
        }
        return resultMap;
    }
}

输出:

{banana=6, apple=11, pear=3}

有一些事情你应该做(如 null 检查),但我不确定这是否是最快的方法。此外,这仅适用于整数。我尝试使用 Number 类的泛型来创建一个方法,但你需要为每种类型(byte、int、short、long 等)编写这个方法。


它可能不是最快的,但对我来说更清晰易懂。非常感谢。 - Melike Ttkn
非常尊敬的您,以下是有关编程的内容的翻译。谢谢。 - Goodlife

1

我改进了Lucas Ross的代码。在函数中,我使用哈希映射的数组列表一次性将所有地图传递给函数,而不是一个一个地输入。

    public HashMap<String, Integer> mergeAndAdd(ArrayList<HashMap<String, Integer>> maplist) {
    HashMap<String, Integer> result = new HashMap<>();
    for (HashMap<String, Integer> map : maplist) {
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            String key = entry.getKey();
            Integer current = result.get(key);
            result.put(key, current == null ? entry.getValue() : entry.getValue() + current);
        }
    }
    return result;
}

}

它也能工作。感谢所有人。


1
假设您有许多HashMap:Map<String,Integer> map1, map2, map3;

然后您可以使用Java 8流:
Map<String,Integer> combinedMap = Stream.of(map1, map2, map3)
                .flatMap(map -> map.entrySet().stream())
                .collect(Collectors.groupingBy(Map.Entry::getKey,
                        Collectors.summingInt(Map.Entry::getValue)));

0

类似这样的代码应该可以运行:

 for (Map.Entry<String, Integer> entry : map.entrySet()) {
    String map1_key = entry.getKey();
    int map1_value = entry.getValue();

    //check:
    if(map2.get(map1_key)!=null){
    int map2_value = map2.get(map1_key);
    //merge:
    map3.put(map1_key,map1_value+map2_value);
    }else{
    map3.put(map1_key,map1_value);
    }
}


  for (Map.Entry<String, Integer> entry2 : map2.entrySet()) {
        String map2_key = entry2.getKey();
        int map2_value = entry2.getValue();

        //check:
        if(map1.get(map2_key)!=null){
        int map1_value = map1.get(map2_key);
        //merge:
        map3.put(map2_key,map1_value+map2_value);
        }else{
        map3.put(map2_key,map2_value);
        }
    }

如果map1中的键在map2中没有值怎么办?那么只存在于map2中的条目呢? - fps
谢谢,但这并不是我问题的确切解决方案。如果所有映射都具有相同的键,则可以正常工作,但如果存在与其他映射不同的键,则无法正常工作。 - Melike Ttkn
哦,抱歉,我以为你说你需要根据相同的键合并值。 - Petro

0
如果键存在,则将其值加上。如果不存在,则插入。
以下是一个简单的示例,将一个映射合并到另一个映射中:
Foo oldVal = map.get(key);
if oldVal == null
{
   map2.put(key, newVal);
}
else
{
   map2.put(key, newVal + oldVal);
}

显然,您必须循环遍历第一个地图,以便您可以处理其所有条目,但这是微不足道的。


我知道,但我不会写。有人问了类似的问题,但是用的是Scala,我不懂Scala。https://dev59.com/pmw05IYBdhLWcg3wuT8L - Melike Ttkn

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