Merge list of maps with duplicate keys

Viewed 1023

I have a list of HashMap<Integer, ArrayList<String>> and would like to merge them in a loop. The problem is that each map's key starts from 0 so the keys will be duplicated. The putAll() does not work as it overrides the keys and always gives me the last map.

I have seen examples of merging two maps by using Stream but in my case, there might be more than 2 maps. I am trying to generate a merged map that has an incremental key. For example:

Let's say I have 2 maps in the list (could be more) and both keys start from 0 but ends at different values.

1st map, the key starts at 0 ends at 10

2nd map, the key starts at 0 ends at 15

Is it possible to add the second map with the key starting at 11? In the end, I need a merged map in which the first key starts at 0 and the last key ends at 25.

4 Answers

I'd iterate over any number of maps you have, and then for each map you want to combine, iterate over the entries. For each entry you can use computeIfAbsent to conditionally create an empty list for the key, and then call addAll on the value. E.g.:

List<Map<Integer, List<String>>> maps = List.of(
        Map.of(1, List.of("hello")),
        Map.of(2, List.of("world")),
        Map.of(1, List.of("a"), 2, List.of("b"))
);

Map<Integer, List<String>> combined = new HashMap<>();
for (Map<Integer, List<String>> map : maps) {
    for (Map.Entry<Integer, List<String>> e : map.entrySet()) {
        combined.computeIfAbsent(e.getKey(), k -> new ArrayList<>()).addAll(e.getValue());
    }
}

Assuming you have a list of maps, where the keys of each map are integers in range [0-k], [0-n], [0, r] ... and your resulting map should a key set in the range of [0 - (k+n+r..)] something like below should work:

public static void main(String[] args) throws IOException {
   //example list of maps
   List<Map<Integer,List<String>>> mapList = List.of(
           Map.of( 0,List.of("foo","foo"), 
                   1,List.of("bar","bar"), 
                   2,List.of("baz","baz")),
           Map.of( 0,List.of("doo","doo"), 
                   1,List.of("gee","gee"), 
                   2,List.of("woo","woo")),
           Map.of( 0,List.of("cab","cab"), 
                   1,List.of("kii","kii"), 
                   2,List.of("taa","taa"))
   );
   AtomicInteger ai = new AtomicInteger();
   Map<Integer,List<String>> result = 
           mapList.stream()
                   .flatMap(map -> map.values().stream())
                   .collect(Collectors.toMap(list -> ai.getAndIncrement(), Function.identity()));
   result.forEach((k,v) ->{
       System.out.println(k + " : " + v);
   });
}

If you prefer a streams approach

Map<Integer, List<String>> m1;
Map<Integer, List<String>> m2;
Map<Integer, List<String>> m3;

Map<Integer, List<String>> combined = new HashMap<>();
Stream
    .of(m1, m2, m3)
    .flatMap(m -> m.entrySet().stream())
    .forEach(e -> combined.computeIfAbsent(e.getKey(), k -> new ArrayList<>())
            .addAll(e.getValue()));

I know you asked for a map, but based on your explanation your ultimate solution and the fact that your keys are now sequential integers starting at 0, you could create just a List<List<String>>. In that case, you could do it like this:

List<List<String>> result = mapList.stream()
               .flatMap(map->map.values().stream())
               .collect(Collectors.toList());                      

Related