web-dev-qa-db-fra.com

Tri Java Objets en utilisant plusieurs clés

J'ai une collection d'objets de canard et je voudrais Triez-les à l'aide de plusieurs clés.

class Duck {
    DuckAge age; //implements Comparable
    DuckWeight weight; //implements Comparable
    String name;
}
List<Duck> ducks = Pond.getDucks();

par exemple. Je veux les trier principalement par leurs poids, et secondairement par leur âge. Si deux canards ont exactement le même poids et exactement le même âge, différentinons-les à l'aide de leur noms comme clé tertiaire. Je pourrais faire quelque chose comme ça:

Collections.sort(ducks, new Comparator<Duck>(){
    @Override
    public int compare(Duck d1, Duck d2){
        int weightCmp = d1.weight.compareTo(d2.weight);
        if (weightCmp != 0) {
            return weightCmp;
        }
        int ageCmp = d1.age.compareTo(d2.age);
        if (ageCmp != 0) {
            return ageCmp;
        }
        return d1.name.compareTo(d2.name);
    }
});

Eh bien, je fais cela assez fréquemment, mais cette solution ne sent pas bien. Cela ne va pas bien, et il est facile de gâcher. Il doit sûrement y avoir une meilleure façon de trier des canards à l'aide de plusieurs clés! Quelqu'un sait-il une meilleure solution?

ÉDITER Supprime inutile else branches

53
andras

Java 8 solution:

Comparator<Duck> cmp = Comparator.comparing(Duck::getWeight)
    .thenComparing(Duck::getAge)
    .thenComparing(Duck::getName);

Hourra pour Lambdas, références de méthodes et méthodes par défaut :)! Dommage que nous devons définir des getters ou utiliser explicite Lambdas , comme:

Comparator<Duck> cmp = Comparator
    .comparing((Duck duck)-> duck.weight)
    .thenComparing((Duck duck)-> duck.age)
    .thenComparing(duck-> duck.name);

Type Inference ne fonctionnera pas avec les Lambdas implicites, vous devez donc spécifier le type d'argument des deux premiers lambdas. Plus de détails dans cette réponse de Brian Goetz .

12
andras

GUAVA est plus élégant:

return ComparisonChain.start()
     .compare(d1.weight, d2.weight)
     .compare(d1.age, d2.age)
     .compare(d1.name, d2.name)
     .result();

Apache Commons-Lang a une construction similaire, CompareToBuilder .

50
JB Nizet
List<Duck> ducks = new ArrayList<Duck>();
Collections.sort(ducks, new Comparator<Duck>() {

  @Override
  public int compare(Duck o1, Duck o2) {

    return new org.Apache.commons.lang.builder.CompareToBuilder().
        append(o1.weight, o2.weight).
        append(o1.age, o2.age).
        append(o1.name, o2.name).
        toComparison();
  }
});
20
Boris Pavlović

Premièrement, votre solution n'est pas ça lent.

Si vous voulez vraiment une autre méthode, donnez à chaque canard un "score" qui est essentiellement un nombre unique qui constitue la somme de leurs trois caractéristiques, mais avec une énorme pondération (excusez le jeu de mots presque inévitable) pour le poids, un moindre pour l'âge ; et un très petit pour le nom.

Vous pouvez allouer ~ 10 bits pour chaque caractéristique, donc pour chaque caractéristique que vous devez être dans la gamme 0..1023.

score = ( (weight << 10) + age) << 10 + name;

Ceci est probablement complètement inutile, mais quoi que ce soit :)

14
Noxville

Vous pouvez utiliser enchaîné BeanComparators des Beantutils des Communes:

Comparator comparator = new BeanComparator("weight", new BeanComparator("age"));

http://commons.apache.org/beanutils/v1.8.3/apidocs/org/apache/commons/beanutils/beancomparator.html

4
Emmanuel Bourg

Je viens de réécriter votre code sans aucune déclaration imbriquée. Aimez-vous cela maintenant?

@Override
public int compare(Duck d1, Duck d2){
    int weightCmp = d1.weight.compareTo(d2.weight);
    if (weightCmp != 0) {
        return weightCmp;
    }
    int ageCmp = d1.age.compareTo(d2.age);
    if (ageCmp != 0) {
        return ageCmp;
    } 

    return d1.name.compareTo(d2.age);
}
4
AlexR