2011-11-07 6 views
47

Ho una raccolta di oggetti Duck e mi piacerebbe ordinare ordinandoli con più chiavi.Ordinamento di oggetti Java utilizzando più chiavi

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

es. Voglio ordinarli principalmente dai loro pesi e secondariamente dalla loro età. Se due anatre hanno esattamente lo stesso peso e la stessa identica età, allora le differenziamo usando i loro nomi come chiave terziaria. Potrei fare qualcosa di simile:

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); 
    } 
}); 

Beh io faccio abbastanza frequentemente, ma questa soluzione non ha odore giusto. Non si adatta bene ed è facile rovinarlo. Sicuramente ci deve essere un modo migliore di ordinare anatre usando più chiavi! Qualcuno sa di una soluzione migliore?

EDIT rimosso inutili else rami

+6

non sembra troppo male, è possibile rimuovere un livello di indentazione, rimuovendo entrambi 'else' poiché in' if' si ritorna, quindi non è necessario. – stivlo

+14

+1 per ottenere le anatre in ordine – Rich

+0

Non esiste una soluzione elegante nello stesso core Java? – Sid

risposta

9

Java 8 soluzione:

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

Urrà per lambda, metodo riferimenti e metodi predefiniti :)!Peccato che dobbiamo definire getter, o utilizzare lambda espliciti, in questo modo:

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

inferenza di tipo non funziona con lambda implicite, quindi si deve specificare il tipo di argomento dei primi due lambda. Maggiori dettagli in this answer by Brian Goetz.

6

È possibile utilizzare l'CompareToBuilder da Apache Commons Lang. (Spiega comparabili, ma funziona anche per Comparator).

48

Guava è più elegante:

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

Apache commons-lang ha un costrutto simile, CompareToBuilder.

+1

grazie, questo è esattamente quello che volevo! La classe [Guava Ordering] (http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/collect/Ordering.html) sembra bella, se ne hai già Comparatori e vogliono combinarli e usarli per l'ordinamento. – andras

+3

CompareToBuilder di Apache è un po 'più elegante, dal momento che gestisce anche i null di default, usando un confronto di null prima. Il Confronto di Guava lancia una NullPointerException a meno che non si aggiunga un terzo parametro (Ordering.natural(). NullsFirst()) a ogni chiamata .compare(). –

+2

Se, sai, ti piacciono i null. –

4

Ho appena riscritto il codice senza ulteriori istruzioni nidificate. Ti piace ora?

@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); 
} 
+0

Sì, grazie, sembra migliore, ma il problema principale era che stavo incatenando i confronti manualmente. Confronto tra Guava ComparisonChain e Apache CompareToBuilder molto meglio. – andras

14

In primo luogo, la soluzione non è quella lento.

Se si vuole davvero un altro metodo, quindi dare ad ogni anatra un "punteggio" che è essenzialmente un singolo numero che è la somma delle loro tre caratteristiche, ma con una ponderazione enorme (scusa il gioco di parole quasi inevitabile) per minore per età; e uno molto piccolo per il nome.

È possibile assegnare ~ 10 bit per ciascuna caratteristica, quindi per ciascuna caratteristica è necessario rientrare nell'intervallo 0..1023.

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

Questo è probabilmente del tutto inutili, ma qualunque :)

+0

Bel trucchetto, grazie. Stavo andando per la bellezza, non per le prestazioni, ma terrò questo a mente :) – andras

20
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(); 
    } 
}); 
+0

Come posso usarlo per ordinare gli Arrayisti? (Usando più chiavi)? –

+0

Stai parlando di array (ad esempio String []) o 'java.util.ArrayList's? Dipende dagli elementi che vengono memorizzati. Le matrici possono essere ordinate usando il metodo 'Arrays.sort (T [], Comparator )' –

+0

Quello che intendevo era, che in questo esempio hai appena ordinato 2 oggetti del tipo duck .... "public int compare (Duck o1, Duck o2) "cosa succede se devo ordinare un intero arraylist di tipo Duck..per es. ArrayList dd = new ArrayList(); ????? –