Merge Map<String, List<String> Java 8 Stream

25,077

Solution 1

You can do this by iterating over all the entries in mapAdded and merging them into mapGlobal.

The following iterates over the entries of mapAdded by calling forEach(action) where the action consumes the key and value of each entry. For each entry, we call merge(key, value, remappingFunction) on mapGlobal: this will either create the entry under the key k and value v if the key didn't exist or it will invoke the given remapping function if they already existed. This function takes the 2 lists to merge, which in this case, are first added to a TreeSet to ensure both unique and sorted elements and converted back into a list:

mapAdded.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. But then, you need to make sure to use a map that supports concurrency for mapGlobal, like a ConcurrentHashMap.

ConcurrentMap<String, List<String>> mapGlobal = new ConcurrentHashMap<>();
// ...
mapAdded.entrySet().parallelStream().forEach(e -> mapGlobal.merge(e.getKey(), e.getValue(), (v1, v2) -> {
    Set<String> set = new TreeSet<>(v1);
    set.addAll(v2);
    return new ArrayList<>(set);
}));

Solution 2

Using foreach over Map can be used to merge given arraylist.

    public Map<String, ArrayList<String>> merge(Map<String, ArrayList<String>> map1, Map<String, ArrayList<String>> map2) {
    Map<String, ArrayList<String>> map = new HashMap<>();
    map.putAll(map1);

    map2.forEach((key , value) -> {
        //Get the value for key in map.
        ArrayList<String> list = map.get(key);
        if (list == null) {
            map.put(key,value);
        }
        else {
            //Merge two list together
            ArrayList<String> mergedValue = new ArrayList<>(value);
            mergedValue.addAll(list);
            map.put(key , mergedValue);
        }
    });
    return map;
}

Solution 3

The original implementation doesn't create result like Map<String, List<Object>>, but Map<String, List<List<String>>>. You need additional Stream pipeline on it to produce Map<String, List<String>>.

Share:
25,077
ypriverol
Author by

ypriverol

Updated on December 24, 2021

Comments

  • ypriverol
    ypriverol over 2 years

    I would like to merge two Map with JAVA 8 Stream:

    Map<String, List<String>> mapGlobal = new HashMap<String, List<String>>();
    Map<String, List<String>> mapAdded = new HashMap<String, List<String>>();
    

    I try to use this implementation:

    mapGlobal = Stream.of(mapGlobal, mapAdded)
                    .flatMap(m -> m.entrySet().stream())
                    .collect(Collectors.groupingBy(Map.Entry::getKey,
                            Collectors.mapping(Map.Entry::getValue,        
                                               Collectors.toList())
                    ));
    

    However, this implementation only create a result like:

    Map<String, List<Object>>

    If one key is not contained in the mapGlobal, it would be added as a new key with the corresponding List of String. If the key is duplicated in mapGlobal and mapAdded, both list of values will be merge as: A = {1, 3, 5, 7} and B = {1, 2, 4, 6} then A ∪ B = {1, 2, 3, 4, 5, 6, 7}.

  • ypriverol
    ypriverol about 8 years
    this implementation will not use the improvements in the stream collections. Both maps can be huge then I would like to use something like parallelStream(). Is that possible.
  • Misha
    Misha about 8 years
    Unless mapGlobal is a ConcurrentMap, mutating it from inside a parallel stream is not safe.
  • Tunaki
    Tunaki about 8 years
    @Misha Correct, hence the suggested use of a ConcurrentHashMap.
  • Misha
    Misha about 8 years
    @Tunaki Well, -1 to me for reading comprehension. I apologize.
  • Tunaki
    Tunaki about 8 years
    @Misha I understand :) and made an edit to make it clearer.