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

Я хотел бы объединить две карты с JAVA 8 Stream:

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

Я пытаюсь использовать эту реализацию:

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

Однако эта реализация создает только результат:

Map<String, List<Object>>

Если один ключ не содержится в mapGlobal, он будет добавлен как новый ключ с соответствующим списком String. Если ключ дублируется в mapGlobal и mapAdded, оба списка значений будут объединены как: A = {1, 3, 5, 7} и B = {1, 2, 4, 6}, затем A ∪ B = {1, 2, 3, 4, 5, 6, 7}.

Ответ 1

Вы можете сделать это, перебирая все записи в mapAdded и объединяя их в mapGlobal.

Далее выполняется перебор записей mapAdded помощью вызова forEach(action) где действие использует ключ и значение каждой записи. Для каждой записи мы вызываем merge(key, value, remappingFunction) для mapGlobal: это либо создаст запись под ключом k и значением v если ключ не существует, либо вызовет заданную функцию переназначения, если они уже существовали. Эта функция объединяет 2 списка, которые в этом случае сначала добавляются в TreeSet для обеспечения уникальности и сортировки элементов и преобразования обратно в список:

mapAdded.forEach((k, v) -> mapGlobal.merge(k, v, (v1, v2) -> {
    Set<String> set = new TreeSet<>(v1);
    set.addAll(v2);
    return new ArrayList<>(set);
}));

Если вы хотите запустить это потенциально параллельно, вы можете создать потоковый конвейер, получив в него entrySet() и вызвав функции parallelStream(). Но тогда вам нужно убедиться, что вы используете карту, поддерживающую параллелизм для mapGlobal, например 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);
}));

Ответ 2

Использование foreach поверх Map может использоваться для объединения заданного массива.

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

Ответ 3

Исходная реализация не создает результат типа Map<String, List<Object>>, но Map<String, List<List<String>>>. Вам понадобится дополнительный конвейер Stream для создания Map<String, List<String>>.