2011-11-07 11 views
47

Tengo una colección de objetos Duck y me gustaría ordenarlos usando múltiples claves.Ordenando objetos Java usando múltiples claves

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

por ejemplo. Quiero ordenarlos principalmente por sus pesos y secundarios por su edad. Si dos patos tienen exactamente el mismo peso y la misma edad exacta, entonces diferenciémoslos usando sus nombres como clave terciaria. Podría hacer algo como esto:

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

Bueno, hago esto con bastante frecuencia, pero esta solución no huele bien. No escala bien, y es fácil equivocarse. ¡Seguramente debe haber una forma mejor de clasificar patos usando múltiples llaves! ¿Alguien sabe de una mejor solución?

EDITAR retira innecesaria else ramas

+6

no se ve tan mal, puede eliminar un nivel de sangría, eliminando ambos 'else' ya que en el' si' devuelve, por lo que no es necesario. – stivlo

+14

+1 para obtener sus patos en orden – Rich

+0

¿No hay una solución elegante en el núcleo de Java en sí? – Sid

Respuesta

9

Java:

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

Hurra por lambdas, referencias de métodos y métodos predeterminados :)!Es una lástima que tenga que definir captadores, o utilizar lambdas explícitas, así:

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

La inferencia de tipos no funcionará con lambdas implícitas, por lo que tiene que especificar el tipo de argumento de los dos primeros lambdas. Más detalles en this answer by Brian Goetz.

48

Guava es más elegante:

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

Apache commons-lang tiene una construcción similar, CompareToBuilder.

+1

gracias, esto es exactamente lo que quería! La orden [de Guava Ordering] (http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/collect/Ordering.html) también se ve bien, si ya tiene alguna Comparadores y desea combinarlos y usarlos para clasificar. – andras

+3

CompareToBuilder de Apache es un poco más elegante, ya que también maneja valores nulos por defecto, usando una primera comparación de nulos. Guava's ComparisonChain arrojará una NullPointerException a menos que agregue un tercer parámetro (Ordering.natural(). NullsFirst()) a cada llamada .compare(). –

+2

Si, ya sabes, te gustan los nulos. –

4

Acabo de volver a escribir su código sin declaraciones anidadas else. ¿Te gusta ahora?

@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í, gracias, se ve mejor, pero el problema principal fue que estaba encadenando comparaciones manualmente. El Guava ComparisonChain y Apache CompareToBuilder se ven mucho mejor. – andras

14

primer lugar, su solución no es que lento.

Si realmente quieres otro método, dale a cada pato un "puntaje" que es esencialmente un único número que es la suma de sus tres características, pero con una gran ponderación (perdón por el juego de palabras casi inevitable), menos uno por edad; y uno muy pequeño para el nombre.

Puede asignar ~ 10 bits para cada característica, por lo que para cada característica debe estar en el rango 0..1023.

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

Ésta es probablemente completamente innecesario, pero cualquiera que sea :) 8 solución

+0

Buen truco, gracias. Me iba por la belleza, no por el rendimiento, pero lo tendré en cuenta :) – 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

¿Cómo puedo usarlo para ordenar las listas de arreglos (usando varias teclas)? –

+0

¿Estás hablando de matrices (por ejemplo, String []) o 'java.util.ArrayList's? Depende de los elementos que están almacenados. Las matrices se pueden ordenar usando el método 'Arrays.sort (T [], Comparador )' –

+0

Lo que quise decir es que en este ejemplo, usted acaba de ordenar 2 objetos del tipo pato ... "public int compare (Pato o1, Duck o2) "¿Qué pasa si tengo que ordenar una lista de arrays completa de tipo Duck ... por ejemplo, ArrayList dd = new ArrayList(); ????? –

Cuestiones relacionadas