Ignorer les doublons lors de la production de cartes à l'aide de flux

257
Map<String, String> phoneBook = people.stream()
                                      .collect(toMap(Person::getName,
                                                     Person::getAddress));

J'obtiens java.lang.IllegalStateException: Duplicate keylorsqu'un élément dupliqué est trouvé.

Est-il possible d'ignorer une telle exception lors de l'ajout de valeurs à la carte?

Lorsqu'il y a un doublon, il devrait simplement continuer en ignorant cette clé en double.

Patan
la source
Si vous pouvez l'utiliser, HashSet ignorera la clé, si elle existe déjà.
sahitya
@ capitaine-aryabhatta. Est-il possible d'avoir des valeurs clés dans hashset
Patan

Réponses:

449

Ceci est possible en utilisant le mergeFunctionparamètre de Collectors.toMap(keyMapper, valueMapper, mergeFunction):

Map<String, String> phoneBook = 
    people.stream()
          .collect(Collectors.toMap(
             Person::getName,
             Person::getAddress,
             (address1, address2) -> {
                 System.out.println("duplicate key found!");
                 return address1;
             }
          ));

mergeFunctionest une fonction qui opère sur deux valeurs associées à la même touche. adress1correspond à la première adresse rencontrée lors de la collecte des éléments et adress2correspond à la deuxième adresse rencontrée: cette lambda indique simplement de conserver la première adresse et ignore la seconde.

Tunaki
la source
6
Je suis confus, pourquoi les valeurs en double (pas les clés) ne sont-elles pas autorisées? Et comment autoriser les valeurs en double?
Hendy Irawan
existe-t-il un moyen de récupérer la clé pour laquelle la collision se produit? répondez ici: stackoverflow.com/questions/40761954/…
Guillaume
2
Est-il possible d'ignorer totalement cette entrée en cas de conflit? Fondamentalement, si je rencontre des clés en double, je ne veux pas qu'elles soient ajoutées du tout. Dans l'exemple ci-dessus, je ne veux pas d'adresse1 ou d'adresse2 dans ma carte.
djkelly99
5
@Hendy Irawan: les valeurs en double sont autorisées. La fonction de fusion consiste à choisir (ou fusionner) deux valeurs qui ont la même clé .
Ricola
3
@ djkelly99 En fait, vous pouvez, il vous suffit de faire revenir votre fonction de remappage null. Voir doc toMap qui pointe pour fusionner le doc qui indique si la fonction de remappage renvoie null, le mappage est supprimé.
Ricola
98

Comme dit dans JavaDocs :

Si les clés mappées contiennent des doublons (selon Object.equals(Object)), un IllegalStateExceptionest levé lorsque l'opération de collecte est effectuée. Si les clés mappées peuvent avoir des doublons, utilisez toMap(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction)plutôt.

Vous devez donc utiliser à la toMap(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction)place. Fournissez simplement une fonction de fusion , qui déterminera lequel des doublons sera placé dans la carte.

Par exemple, si vous ne vous souciez pas lequel, appelez simplement

Map<String, String> phoneBook = 
        people.stream()
              .collect(Collectors.toMap(Person::getName, 
                                        Person::getAddress, 
                                        (a1, a2) -> a1));
alaster
la source
8

La réponse @alaster m'aide beaucoup, mais je voudrais ajouter une information significative si quelqu'un essaie de regrouper l'information.

Si vous en avez par exemple deux Ordersavec des produits identiques codemais différents quantitypour chacun, et que votre désir est de faire la somme des quantités, vous pouvez faire:

List<Order> listQuantidade = new ArrayList<>();
listOrders.add(new Order("COD_1", 1L));
listOrders.add(new Order("COD_1", 5L));
listOrders.add(new Order("COD_1", 3L));
listOrders.add(new Order("COD_2", 3L));
listOrders.add(new Order("COD_3", 4L));

listOrders.collect(Collectors.toMap(Order::getCode, 
                                    o -> o.getQuantity(), 
                                    (o1, o2) -> o1 + o2));

Résultat:

{COD_3=4, COD_2=3, COD_1=9}
Dherik
la source
2

Pour toute autre personne rencontrant ce problème mais sans que les clés en double dans la carte ne soient diffusées, assurez-vous que votre fonction keyMapper ne renvoie pas de valeurs nulles .

Il est très ennuyeux de retrouver cela car l'erreur indiquera "Duplicate key 1" lorsque 1 est en fait la valeur de l'entrée au lieu de la clé.

Dans mon cas, ma fonction keyMapper a essayé de rechercher des valeurs dans une carte différente, mais en raison d'une faute de frappe dans les chaînes, elle renvoyait des valeurs nulles.

final Map<String, String> doop = new HashMap<>();
doop.put("a", "1");
doop.put("b", "2");

final Map<String, String> lookup = new HashMap<>();
doop.put("c", "e");
doop.put("d", "f");

doop.entrySet().stream().collect(Collectors.toMap(e -> lookup.get(e.getKey()), e -> e.getValue()));
Andrew
la source
1

Pour regrouper par objets

Map<Integer, Data> dataMap = dataList.stream().collect(Collectors.toMap(Data::getId, data-> data, (data1, data2)-> {LOG.info("Duplicate Group For :" + data2.getId());return data1;}));
fjkjava
la source
0

J'ai rencontré un tel problème lors du regroupement d'objets, je les ai toujours résolus d'une manière simple: effectuez un filtre personnalisé en utilisant un java.util.Set pour supprimer l'objet en double avec l'attribut de votre choix comme ci-dessous

Set<String> uniqueNames = new HashSet<>();
Map<String, String> phoneBook = people
                  .stream()
                  .filter(person -> person != null && !uniqueNames.add(person.getName()))
                  .collect(toMap(Person::getName, Person::getAddress));

J'espère que cela aide toute personne ayant le même problème!

Shessuky
la source
-1

En supposant que vous avez des personnes est une liste d'objets

  Map<String, String> phoneBook=people.stream()
                                        .collect(toMap(Person::getName, Person::getAddress));

Maintenant, vous avez besoin de deux étapes:

1)

people =removeDuplicate(people);

2)

Map<String, String> phoneBook=people.stream()
                                        .collect(toMap(Person::getName, Person::getAddress));

Voici la méthode pour supprimer les doublons

public static List removeDuplicate(Collection<Person>  list) {
        if(list ==null || list.isEmpty()){
            return null;
        }

        Object removedDuplicateList =
                list.stream()
                     .distinct()
                     .collect(Collectors.toList());
     return (List) removedDuplicateList;

      }

Ajout d'un exemple complet ici

 package com.example.khan.vaquar;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class RemovedDuplicate {

    public static void main(String[] args) {
        Person vaquar = new Person(1, "Vaquar", "Khan");
        Person zidan = new Person(2, "Zidan", "Khan");
        Person zerina = new Person(3, "Zerina", "Khan");

        // Add some random persons
        Collection<Person> duplicateList = Arrays.asList(vaquar, zidan, zerina, vaquar, zidan, vaquar);

        //
        System.out.println("Before removed duplicate list" + duplicateList);
        //
        Collection<Person> nonDuplicateList = removeDuplicate(duplicateList);
        //
        System.out.println("");
        System.out.println("After removed duplicate list" + nonDuplicateList);
        ;

        // 1) solution Working code
        Map<Object, Object> k = nonDuplicateList.stream().distinct()
                .collect(Collectors.toMap(s1 -> s1.getId(), s1 -> s1));
        System.out.println("");
        System.out.println("Result 1 using method_______________________________________________");
        System.out.println("k" + k);
        System.out.println("_____________________________________________________________________");

        // 2) solution using inline distinct()
        Map<Object, Object> k1 = duplicateList.stream().distinct()
                .collect(Collectors.toMap(s1 -> s1.getId(), s1 -> s1));
        System.out.println("");
        System.out.println("Result 2 using inline_______________________________________________");
        System.out.println("k1" + k1);
        System.out.println("_____________________________________________________________________");

        //breacking code
        System.out.println("");
        System.out.println("Throwing exception _______________________________________________");
        Map<Object, Object> k2 = duplicateList.stream()
                .collect(Collectors.toMap(s1 -> s1.getId(), s1 -> s1));
        System.out.println("");
        System.out.println("k2" + k2);
        System.out.println("_____________________________________________________________________");
    }

    public static List removeDuplicate(Collection<Person> list) {
        if (list == null || list.isEmpty()) {
            return null;
        }

        Object removedDuplicateList = list.stream().distinct().collect(Collectors.toList());
        return (List) removedDuplicateList;

    }

}

// Model class
class Person {
    public Person(Integer id, String fname, String lname) {
        super();
        this.id = id;
        this.fname = fname;
        this.lname = lname;
    }

    private Integer id;
    private String fname;
    private String lname;

    // Getters and Setters

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getFname() {
        return fname;
    }

    public void setFname(String fname) {
        this.fname = fname;
    }

    public String getLname() {
        return lname;
    }

    public void setLname(String lname) {
        this.lname = lname;
    }

    @Override
    public String toString() {
        return "Person [id=" + id + ", fname=" + fname + ", lname=" + lname + "]";
    }

}

Résultats :

Before removed duplicate list[Person [id=1, fname=Vaquar, lname=Khan], Person [id=2, fname=Zidan, lname=Khan], Person [id=3, fname=Zerina, lname=Khan], Person [id=1, fname=Vaquar, lname=Khan], Person [id=2, fname=Zidan, lname=Khan], Person [id=1, fname=Vaquar, lname=Khan]]

After removed duplicate list[Person [id=1, fname=Vaquar, lname=Khan], Person [id=2, fname=Zidan, lname=Khan], Person [id=3, fname=Zerina, lname=Khan]]

Result 1 using method_______________________________________________
k{1=Person [id=1, fname=Vaquar, lname=Khan], 2=Person [id=2, fname=Zidan, lname=Khan], 3=Person [id=3, fname=Zerina, lname=Khan]}
_____________________________________________________________________

Result 2 using inline_______________________________________________
k1{1=Person [id=1, fname=Vaquar, lname=Khan], 2=Person [id=2, fname=Zidan, lname=Khan], 3=Person [id=3, fname=Zerina, lname=Khan]}
_____________________________________________________________________

Throwing exception _______________________________________________
Exception in thread "main" java.lang.IllegalStateException: Duplicate key Person [id=1, fname=Vaquar, lname=Khan]
    at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)
    at java.util.HashMap.merge(HashMap.java:1253)
    at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
    at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
    at com.example.khan.vaquar.RemovedDuplicate.main(RemovedDuplicate.java:48)
vaquar khan
la source