4
votes

Essayer de passer un comparateur via le constructeur d'un HashSet

Alors que j'essaie de comprendre comment utiliser correctement le comparateur , j'essaie de trier mes employés dans un HashSet . J'ai donc fait ceci:

Queue<Employee> list = new PriorityQueue<Employee>((a,b)->a.getAge()-b.getAge());

Comme vous pouvez le voir, j'ai essayé de le trier par âge, mais lorsque j'utilise cette expression lambda, cela produit une erreur de compilation, donc je suppose que quelque chose ne va pas. t ici.

Voici ma classe Employee :

class Employee {    
    String name;
    int age;
    // constructor, getters and setters
}

Modifier : p>

Avec une PriorityQueue, cela fonctionne parfaitement:

Set<Employee> EmployeeSet = new HashSet<Employee>((a,b)->a.getAge()-b.getAge());

Pourquoi?


4 commentaires

" ... ça fait une erreur ... " - Qu'est-ce que ça veut dire? Erreur de compilation? Erreur d'exécution? Mauvais ordre? Veuillez être précis.


@ Turing85 À peu près sûr qu'une erreur de compilation est assez évidente là-bas si le même code est utilisé. Juste une opinion cependant.


@nullpointer oui, c'est évident si vous connaissez les tenants et les aboutissants de l'API Java. Mais tout le monde ne le fait pas. De plus, le site sur le sujet indique clairement que les questions de débogage " doivent inclure [...] un problème spécifique [...] ".


Les HashSets sont des collections non ordonnées.


3 Réponses :


6
votes

Vous pouvez utiliser un TreeSet qui garantit un Set ordonné basé sur le Comparator

Queue<Employee> list = new PriorityQueue<>(Comparator.comparingInt(Employee::getAge));

Le HashSet d'autre part n'accepte pas un comparateur dans son constructeur pour l'initialisation.

Modifier :

Set<Employee> employeeSet = new TreeSet<>(Comparator.comparingInt(Employee::getAge));
// (a, b) -> a.getAge() - b.getAge() >>> Comparator.comparingInt(Employee::getAge

fonctionne bien puisque, PriorityQueue est à nouveau une collection ordonnée qui accepte un comparateur dans l'un des ses constructeurs.


1 commentaires

Si vous vous demandez toujours, commandé et la collecte non ordonnée est la principale différence ici.



1
votes

D'un point de vue mécanique, un HashSet ne repose pas sur un comparateur ou sur l'interface Comparable pour déterminer l'ordre ou l'unicité; à la place, il repose sur le hashCode des éléments individuels que vous placez à l'intérieur.

C'est pourquoi vous ne pouvez pas l'ajouter comme paramètre de constructeur; cela n'a aucun sens d'essayer d'insérer dans une carte de hachage des valeurs qui ont un ordre naturel. Sauf indication contraire de l'implémentation de l'ensemble, les ensembles sont par défaut non ordonnés ; HashSet est une implémentation non ordonnée d'un ensemble.

La raison pour laquelle PriorityQueue et TreeMap fonctionnent avec des comparateurs et des éléments Comparables est due à la façon dont ils sont structurés. Les éléments de commande PriorityQueue et TreeMap dans l'ordre naturel , donc avoir un Comparateur pour piloter cela en l'absence de vos éléments implémentant Comparable peut être souhaitable. Ces structures de données sont ordonnées , donc imposer un ordre personnalisé est une fonctionnalité pour celles-ci.


0 commentaires

3
votes

Comme d'autres l'ont noté, HashSet est un ensemble non ordonné, et il ne peut donc pas prendre un comparateur qui fournit un ordre de tri. Vous voudrez peut-être utiliser un ensemble trié tel que TreeSet à la place.

    [Employee{name=Chris, age=26}, Employee{name=Kelly, age=26}, Employee{name=Brett, age=32}, Employee{name=Terry, age=37}]

Une note rapide sur les comparaisons numériques. L'utilisation de la soustraction pour comparer les valeurs int est dangereuse, car elle peut donner des résultats incorrects si la soustraction déborde. Cela ne se produira pas avec l'âge des personnes dont la fourchette typique n'entraînera pas de débordement. Mais si quelqu'un copiait ce code et l'appliquait à des valeurs différentes, cela pourrait entraîner des bogues. Au lieu de cela, je recommande d'utiliser les méthodes utilitaires Comparator pour construire la bonne fonction de comparaison:

    Set<Employee> employeeSet = new TreeSet<>(Comparator.comparingInt(Employee::getAge)
                                                        .thenComparing(Employee::getName));

Cela triera effectivement les employés par âge. Cependant ...

    employeeList.sort(Comparator.comparingInt(Employee::getAge));

    // before
    [Employee{name=Terry, age=37}, Employee{name=Kelly, age=26}, Employee{name=Brett, age=32}, Employee{name=Chris, age=26}]

    // after
    [Employee{name=Kelly, age=26}, Employee{name=Chris, age=26}, Employee{name=Brett, age=32}, Employee{name=Terry, age=37}]

Nous avons ajouté quatre employés, mais il n'y a que trois employés dans l'ensemble! Aussi:

    System.out.println(employeeSet.contains(kelly)); // true
    System.out.println(employeeSet.contains(chris)); // true

Comment est-ce possible? Le comparateur fourni est utilisé non seulement pour la commande , mais il est également utilisé pour déterminer l'appartenance à l'ensemble . Puisque Kelly et Chris ont le même âge, ce comparateur les considère comme égaux, donc un seul d'entre eux se retrouve dans l'ensemble.

Vous ne voulez probablement pas utiliser un TreeSet pour faire le tri par valeurs qui peuvent avoir des doublons, car cela entraînera la perte d'éléments. Vous pouvez faire plusieurs choses. Premièrement, vous pouvez mettre les employés dans une liste puis trier la liste:

    Employee terry = new Employee("Terry", 37);
    Employee kelly = new Employee("Kelly", 26);
    Employee brett = new Employee("Brett", 32);
    Employee chris = new Employee("Chris", 26);
    List<Employee> employeeList = Arrays.asList(terry, kelly, brett, chris);
    Set<Employee> employeeSet = new TreeSet<>(Comparator.comparingInt(Employee::getAge));
    employeeSet.addAll(employeeList);
    System.out.println(employeeSet);

    [Employee{name=Kelly, age=26}, Employee{name=Brett, age=32}, Employee{name=Terry, age=37}]

Ou, vous pouvez écrire un comparateur plus complexe qui différencie chaque élément des données d'entrée, de sorte qu'aucun deux ne soit considéré comme un double. Dans un exemple réaliste, les employés peuvent avoir le même nom, nous pouvons donc également souhaiter inclure une comparaison de quelque chose de vraiment unique, comme un identifiant d'employé. Cependant, pour cet exemple simple, nous pouvons simplement trier le nom de l'employé:

    Set<Employee> employeeSet = new TreeSet<>(Comparator.comparingInt(Employee::getAge));

Lorsque tous les employés sont ajoutés, le résultat est:

    Set<Employee> employeeSet = new TreeSet<>((a,b)->a.getAge()-b.getAge());


0 commentaires