Sortowanie obiektów Java przy użyciu wielu kluczy

Mam kolekcję obiektów kaczych i chciałbym posortować je za pomocą wielu klawiszy .

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

Np. Chcę je posortować przede wszystkim według ich wagi , a Po drugie według ich wieku . Jeśli dwie kaczki mają dokładnie taką samą wagę i ten sam wiek, to rozróżnijmy je używając ich nazw jako klucz trzeciorzędowy . Mogę zrobić coś takiego:

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);
    }
});
Cóż, robię to dość często, ale to rozwiązanie nie pachnie racja. Nie skaluje się zbyt dobrze i łatwo się zepsuć. Na pewno musi być lepszy sposób sortowania kaczek za pomocą wielu klawiszy! Czy ktoś zna lepsze rozwiązanie?

Edytuj Usunięto niepotrzebne else gałęzie

Author: Rich, 2011-11-07

7 answers

Guawa jest bardziej elegancka:

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

Apache commons-lang ma podobną konstrukcję, CompareToBuilder.

 52
Author: JB Nizet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-15 21:42:07
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();
  }
});
 21
Author: Boris Pavlović,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-07 12:30:46

Java 8 rozwiązanie:

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

Hura dla Lambda, referencji metod i domyślnych metod:)! Szkoda, że musimy zdefiniować gettery, lub użyć explicit lambda , w ten sposób:

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

Wnioskowanie typu nie będzie działać z domyślnymi lambdami, więc musisz podać typ argumentu dwóch pierwszych lambd. Więcej szczegółów w ta odpowiedź Brian Goetz .

 17
Author: andras,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:32

Po pierwsze, twoje rozwiązanie nie jestże powolne.

Jeśli naprawdę chcesz innej metody, to daj każdej kaczce "wynik" , który jest zasadniczo pojedynczą liczbą, która jest sumą ich trzech cech, ale z ogromną wagą (wybacz prawie nieunikniony kalambur) dla wagi, mniejszą dla wieku; i bardzo małą dla nazwy.

Możesz przydzielić ~10 bitów dla każdej charakterystyki, więc dla każdej charakterystyki musisz być w zakresie 0..1023.

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

To jest prawdopodobnie zupełnie niepotrzebne, ale co tam:)

 14
Author: Noxville,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-03 14:04:38

Możesz użyć CompareToBuilder z Apache Commons Lang . (Wyjaśnia porównywalne, ale działa również w przypadku komparatora).

 6
Author: Thirler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-07 12:26:24

Możesz użyć chained BeanComparators z Commons BeanUtils:

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

Http://commons.apache.org/beanutils/v1.8.3/apidocs/org/apache/commons/beanutils/BeanComparator.html

 4
Author: Emmanuel Bourg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-07 12:28:10

Właśnie przepisałem Twój kod bez zagnieżdżonych instrukcji else. Podoba Ci się teraz?

@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
Author: AlexR,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-07 12:29:14