'How to merge multiple Maps having the same keys but different values into a single Map

I have n maps of the kind:

HashMap<String,Double> map1;
HashMap<String,Double> map2; ...

What could I do to merge all these maps into a single map?

And I want to achieve this without losing the data.

Example:

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>}

I have tried this:

ArrayList<HashMap<String, Double>> listHashMap = new ArrayList<>();
            
HashMap<String,Double> mergedMap = new HashMap<>();
for(HashMap<String,Double> map: listHashMap) {  
    mergedMap.putAll(map);          
}

But I've understood that the values mapped to the same key would be replaced (put), not added.

What could I do to add up each value mapped to the same key?



Solution 1:[1]

You can utilize Java 8 method merge() to combine the data of each entry:

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)));

See that code run live at Ideone.com.

Or you can make use of Stream API with combination of built-in collectors 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)
            ));

Take a look at these tutorials provided by Oracle for more information on lambda expressions and streams.

Solution 2:[2]

Using stream you could do:

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));

Solution 3:[3]

One option is to use the Map.compute method. Its second argument is a remapping function that can be used like in the following example:

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);

oldValue is null if there is not yet an entry with key in mergedMap, and the old value otherwise.

This prints:

{X1=12.0, X2=3.0, X3=9.0}

Solution 4:[4]

As others have pointed in the comments, a Map cannot have multiple keys with the same value, so I've associated to each key a List of double values to keep it as close as possible to your example.

To achieve your goal, you could stream the entries of both of your Maps and collect them with the collect(Collectors.toMap()). The keys would stay the same, the content of the Lists would be summed and when keys collide you could simply sum the double values obtained from the first and second colliding 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));

Solution 5:[5]

Update: I might prefer the concise solution by Alexander Ivanchenko. I will leave this Answer posted as an interesting or educational alternative.


Your example data is flawed (repeated keys), so I crafted another set of data.

Map < String, Double > map1 =
        Map.of(
                "X1" , 1d ,
                "X2" , 1d
        );

Map < String, Double > map2 =
        Map.of(
                "X1" , 2d ,
                "X2" , 2d ,
                "X3" , 7d
        );

Define a new Map to hold results.

Map < String, Double > results = new HashMap <>();

Stream & Map#getOrDefault

Process the two input maps by making a stream of each, joining those streams into a single stream. For each map entry in that stream, put the key into the new map, with a value of either the entry's value upon first occurrence or adding the entry's value to the previously put value.

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 );

See this code run live at Ideone.com.

map = {X1=3.0, X2=3.0, X3=7.0}

Without streams

If not yet familiar with streams, you could instead use a pair of for-each loops to process each of the two input maps.

Process the first map.

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.
}

And do the same for the second input map. The only difference here is the first line, map1.entrySet() becomes 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.
}

Solution 6:[6]

Here is a "You can't teach and old dog new tricks" way of doing it:

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;
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Basil Bourque
Solution 2 Eritrean
Solution 3
Solution 4
Solution 5
Solution 6 James