web-dev-qa-db-fra.com

Fusionner la carte <chaîne, liste <chaîne> Java 8 Stream

Je voudrais fusionner deux Map avec Java 8 Stream:

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

J'essaie d'utiliser cette implémentation:

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

Cependant, cette implémentation ne crée qu'un résultat comme:

Map<String, List<Object>>

Si une clé n'est pas contenue dans mapGlobal, elle sera ajoutée en tant que nouvelle clé avec la liste de chaînes correspondante. Si la clé est dupliquée dans mapGlobal et mapAdded, les deux listes de valeurs seront fusionnées comme: A = {1, 3, 5, 7} et B = {1, 2, 4, 6} puis A ∪ B = {1, 2, 3, 4, 5, 6, 7}.

16
ypriverol

Vous pouvez le faire en itérant sur toutes les entrées de mapAdded et en les fusionnant dans mapGlobal.

L'exemple suivant parcourt les entrées de mapAdded en appelant forEach(action) où l'action consomme la clé et la valeur de chaque entrée. Pour chaque entrée, nous appelons merge(key, value, remappingFunction) sur mapGlobal: cela créera soit l'entrée sous la clé k et la valeur v si la clé n'existait pas ou qu'elle invoquerait la fonction de remappage donnée si elle existait déjà. Cette fonction prend les 2 listes à fusionner, qui dans ce cas, sont d'abord ajoutées à un TreeSet pour garantir à la fois les éléments uniques et triés et reconverties en liste:

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

Si vous souhaitez exécuter cela potentiellement en parallèle, vous pouvez créer un pipeline Stream en obtenant la entrySet() et en appelant parallelStream() dessus. Mais alors, vous devez vous assurer d'utiliser une carte qui prend en charge la concurrence pour mapGlobal, comme un 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);
}));
19
Tunaki

L'utilisation de foreach sur Map peut être utilisée pour fusionner une liste d'arrays donnée.

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

L'implémentation d'origine ne crée pas de résultat comme Map<String, List<Object>>, mais Map<String, List<List<String>>>. Vous avez besoin d'un pipeline Stream supplémentaire pour produire Map<String, List<String>>.

2
Sergei Veselev

Utilisation de StreamEx

Map<String, List<String>> mergedMap =
        EntryStream.of(mapGlobal)
                .append(EntryStream.of(mapAdded))
                .toMap((v1, v2) -> {
                    List<String> combined = new ArrayList<>();
                    combined.addAll(v1);
                    combined.addAll(v2);
                    return combined;
                });

Si vous avez encore plus de cartes à fusionner, ajoutez-les simplement au flux

                .append(EntryStream.of(mapAdded2))
                .append(EntryStream.of(mapAdded3))
0
Jay