web-dev-qa-db-fra.com

Fusionner la carte de tableaux avec des clés en double

J'ai deux cartes de tableaux. 

Map<String, List<String>> map1 = new HashMap<>();
Map<String, List<String>> map2 = new HashMap<>();

Je veux les fusionner dans une nouvelle carte.
Si une clé existe dans les deux cartes, dans ce cas, je devrais fusionner des tableaux. 

Par exemple:

map1.put("k1", Arrays.asList("a0", "a1"));
map1.put("k2", Arrays.asList("b0", "b1"));

map2.put("k2", Arrays.asList("z1", "z2"));

// Expected output is 
Map 3: {k1=[a0, a1], k2=[b0, b1, z1, z2]}

J'ai essayé de faire ça avec des ruisseaux 

Map<String, List<String>> map3 = Stream.of(map1, map2)
    .flatMap(map -> map.entrySet().stream())
    .collect(Collectors.toMap(
        Map.Entry::getKey,
        e -> e.getValue().stream().collect(Collectors.toList())
    ));

Cela fonctionne s'il n'y a pas les mêmes clés dans les cartes. Sinon, je reçois l'exception 

Exception in thread "main" Java.lang.IllegalStateException: Duplicate key k2 (attempted merging values [b0, b1] and [z1, z2])
    at Java.base/Java.util.stream.Collectors.duplicateKeyException(Collectors.Java:133)
    at Java.base/Java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.Java:180)
    at Java.base/Java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.Java:169)
    at Java.base/Java.util.HashMap$EntrySpliterator.forEachRemaining(HashMap.Java:1751)
    at Java.base/Java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.Java:658)
    at Java.base/Java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.Java:274)
    at Java.base/Java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.Java:948)
    at Java.base/Java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.Java:484)
    at Java.base/Java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.Java:474)
    at Java.base/Java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.Java:913)
    at Java.base/Java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.Java:234)
    at Java.base/Java.util.stream.ReferencePipeline.collect(ReferencePipeline.Java:578)
    at im.djm.Test.main(Test.Java:25)

Est-il possible d'accomplir cette tâche avec des flux?
Ou je dois parcourir des cartes? 

7
djm.im

Utilisez une fonction de fusion dans le cas de clés en double:

Map<String, List<String>> map3 = Stream.of(map1, map2)
                .flatMap(map -> map.entrySet().stream())
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        e -> new ArrayList<>(e.getValue()),
                        (left, right) -> {left.addAll(right); return left;}
                ));

Notez que j'ai changé e -> e.getValue().stream().collect(Collectors.toList()) en new ArrayList<>(e.getValue()) pour garantir que nous avons toujours une liste modifiable que nous pouvons ajouter à la fonction de fusion. 

8
Aomine

Peut être. Mais vous aurez plus de chance de tout réussir en combinant les entrées manuellement, en utilisant l'itération. Je ne sais pas si quelqu'un d'autre devra travailler sur ce code, mais ils seront probablement reconnaissants pour une approche facile à lire.

4
Steve11235

Vous pouvez aussi le faire comme ça:

Map<String, List<String>> map3 = Stream.concat(map1.entrySet().stream(),
                                               map2.entrySet().stream())
      .collect(Collectors.groupingBy(Entry::getKey,
                   Collectors.mapping(Entry::getValue,
                       Collectors.flatMapping(List::stream,
                           Collectors.toList()))));
4
Andreas

Vous devez utiliser la version surchargée toMap() qui permet de fusionner des clés en double: 

toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper,
                                    BinaryOperator<U> mergeFunction) 

Vous pouvez écrire quelque chose comme:

Map<String, List<String>> map3 = Stream.of(map1, map2)
    .flatMap(map -> map.entrySet().stream())
    .collect(Collectors.toMap(
        Map.Entry::getKey,
        e -> new ArrayList<>(e.getValue()),
        (e1, e2) -> { e1.addAll(e2); return e1;}
    ));
2
davidxxx

Utiliser une flatmap deux fois

Map<String, List<String>> map1 = new HashMap<>();
Map<String, List<String>> map2 = new HashMap<>();

map1.put("k1", Arrays.asList("a0", "a1"));
map1.put("k2", Arrays.asList("b0", "b1"));

map2.put("k2", Arrays.asList("z1", "z2"));

Map<String, List<String>> map3 = Stream.of(map1, map2)
        .flatMap(p -> p.entrySet().stream())
        .flatMap(p -> p.getValue().stream().map(q -> new Pair<>(p.getKey(), q)))
        .collect(
                Collectors.groupingBy(
                        p -> p.getKey(),
                        Collectors.mapping(p -> p.getValue(), Collectors.toList())
                )
        );

Cela fonctionne comme ceci:

  • Prend les deux cartes Stream<Map<String,List<String>>>
  • FlatMaps les entrées en tant que Entry<String, List<String>>
  • FlatMaps les entrées en 1 paire par Pair<String, String>
  • Les recueille par leur clé
    • Prendre les valeurs et les rassembler dans une liste
2
jrtapsell

autrement serait comme ça. 

vous devriez initier map3 avec une carte plus grande (ici map1). puis utilisez boucle sur une autre carte et utilisez la méthode merge pour combiner la clé en double.

Map<String, List<String>> map3 = new HashMap<>(map1);
    for (Map.Entry<String, List<String>> entry : map2.entrySet()) {
       List<String> values = new ArrayList<>(entry.getValue());
       map3.merge(entry.getKey(),entry.getValue(),(l1, l2) -> {values.addAll(l1); 
           return values;
       });
    }

map2.forEach((key, value) -> {
    List<String> values = new ArrayList<>(value);
      map3.merge(key,value, (l1, l2) -> {values.addAll(l1);return values;});
});
0
Hadi J

Voici un autre moyen de fusionner des cartes et des listes.

Map<String, List<String>> map3 = Stream.of(map1, map2)
    .flatMap(map -> map.entrySet().stream())
    .collect(Collectors.toMap(
        Map.Entry::getKey,
        Map.Entry::getValue,
        (e1, e2) -> Stream.concat(e1.stream(), e2.stream()).collect(Collectors.toList())
    ));

Le troisième argument de la méthode toMap est
(e1, e2) -> Stream.concat(e1.stream(), e2.stream()).collect(Collectors.toList()) est une fonction mergeFunction.
Cette fonction est appliquée aux doublons. 

Si les clés mappées contiennent des doublons (selon Object.equals(Object)), la fonction de mappage de valeur est appliquée à chaque élément égal et les résultats sont fusionnés à l'aide de la fonction de fusion fournie.
JavaDoc

0
djm.im

Voici un exemple utilisant l'itération des deux cartes. La première itération joint les paires clé/valeur communes de map1 et map2 et les ajoute à la carte résultante ou ajoute des paires clé/valeur uniques dans map1 à la carte résultante. La deuxième itération récupère tout ce qui reste dans map2 qui ne correspond pas à map1 et les ajoute à la carte résultante.

public static Map<String, ArrayList<String>> joinMaps(Map<String, ArrayList<String>> map1, Map<String, ArrayList<String>> map2)
{
    Map<String, ArrayList<String>> mapJoined = new HashMap<>();

    //join values from map2 into values of map1 or add unique key/values of map1
    for (Map.Entry<String, ArrayList<String>> entry : map1.entrySet()) {
        String key = entry.getKey();
        ArrayList<String> value = entry.getValue();
        if(map2.containsKey(key))
        {
            value.addAll(map2.get(key));
            mapJoined.put(key, value);
        }
        else
            mapJoined.put(key, value);
    }

    //add the non-duplicates left over in map 2
    for (Map.Entry<String, ArrayList<String>> entry : map2.entrySet()) {
        if(!mapJoined.containsKey(entry.getKey()))
            mapJoined.put(entry.getKey(), entry.getValue());
    }

    return mapJoined;
}

Vous pouvez également ajouter un ensemble dans la fonction pour garder une trace de toutes les clés ajoutées lors de la première itération. Si la taille de cet ensemble est == taille de la carte2, vous savez que les cartes ont les mêmes clés et il n'est pas nécessaire de les parcourir en deuxième. carte, map2.

0
RAZ_Muh_Taz