Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merge list of maps into a single map by summing values

I have a class Values:

public class Values {
    private int count;
    private int values;
}

And a list of multiple maps of type Map<String, Values>

Map<String, Values> map1 = new HashMap<>();
        map1 .put("aaa", new Values(1, 10));
        map1 .put("bbb", new Values(5, 50));
        map1 .put("ccc", new Values(2, 30));

Map<String, Values> map2= new HashMap<>();
        map2.put("aaa", new Values(2, 20));
        map2.put("bbb", new Values(3, 50));
        map2.put("ccc", new Values(3, 10));

List<Map<String, Values>> list = Arrays.asList(map1, map2);

There could be any number of maps and any number of entries inside the maps, but the keys for the maps are always the same, only the values can differ. My example contains only 2 maps and only 3 entries for each map for clarity.

I want to obtain a single map with the same keys and with the Values objects as the sum of each "count" and each "value" in the objects, like this:

{
        aaa= {
            count = 3,
            value = 30
        },
        bbb= {
            count = 8,
            value = 100
        },
        ccc= {
            count = 6,
            value = 40
        }
    }

I am trying to achieve this using the Streams API, but I am stuck:

public static Map<String, Values> mergeMaps(List<Map<String, Values>> maps) {
        return maps.stream()
                .flatMap(m -> m.entrySet().stream()) ...?
    }

How can I group each entry by their keys and add up each count and each value into a single map?

Thank you in advance.

like image 693
stk020305 Avatar asked Feb 10 '19 09:02

stk020305


People also ask

How do I merge a list of maps?

forEach((k, v) -> mapGlobal. merge(k, v, (v1, v2) -> { Set<String> set = new TreeSet<>(v1); set. addAll(v2); return new ArrayList<>(set); })); If you want to run that potentially in parallel, you can create a Stream pipeline by getting the entrySet() and calling parallelStream() on it.

How do I put multiple maps on one map?

concat() Alternatively, we can use Stream#concat() function to merge the maps together. This function can combine two different streams into one. As shown in the snippet, we are passed the streams of map1 and map2 to the concate() function and then collected the stream of their combined entry elements.

What does the merge () method on map do?

Java HashMap merge() The Java HashMap merge() method inserts the specified key/value mapping to the hashmap if the specified key is already not present. If the specified key is already associated with a value, the method replaces the old value with the result of the specified function.

How do you sum a value on a map?

To get the sum of all values in a Map : Use the forEach() method to iterate over the Map . On each iteration, add the number to the sum , reassigning the variable.


Video Answer


2 Answers

You can collect the entries of your Stream with a toMap collector, with a merge function.

public static Map<String, Values> mergeMaps(List<Map<String, Values>> maps) {
    return maps.stream()
               .flatMap(m -> m.entrySet().stream())
               .collect(Collectors.toMap(Map.Entry::getKey,
                                         Map.Entry::getValue,
                                         (v1,v2) -> new Values(v1,v2)));
}

Assuming you have a Values constructor that takes two Values instances and creates an instance having the sums of the values.

Of course, you can write the merge function without that constructor. For example:

(v1,v2) -> new Values(v1.getCount()+v2.getCount(),v1.getValue()+v2.getValue())
like image 161
Eran Avatar answered Nov 15 '22 16:11

Eran


One more solution with groupingBy:

Map<String, Optional<Values>> collect = list.stream()
            .flatMap(map -> map.entrySet().stream())
            .collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue,
                    reducing((v1, v2) -> new Values(v1.count + v2.count, v1.values + v2.values)))));

Note: values of this map are Optional<Values>. If you have null value on one of your source map like map2.put("ddd", null); it allows to avoid NullPointerException and return Optional.empty

like image 31
Ruslan Avatar answered Nov 15 '22 18:11

Ruslan