tipos - Clasificación de objetos Java usando múltiples claves
variables de referencia en java (7)
Acabo de reescribir tu código sin declaraciones else anidadas. ¿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);
}
Tengo una colección de objetos Duck y me gustaría ordenarlos usando varias claves .
class Duck {
DuckAge age; //implements Comparable
DuckWeight weight; //implements Comparable
String name;
}
List<Duck> ducks = Pond.getDucks();
p.ej. Quiero clasificarlos principalmente por su peso , y secundariamente 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?
EDIT elimina else
ramas innecesarias
En primer lugar, tu solución no es tan lenta.
Si realmente quieres otro método, dale a cada pato una "puntuación" 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 ineludible) para el peso, una menor para la 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;
Esto es probablemente completamente innecesario, pero sea lo que sea :)
Puede usar BeanComparators
encadenados de 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
Puede usar CompareToBuilder de Apache Commons Lang . (Explica comparable, pero también funciona para Comparator).
Solución Java 8:
Comparator<Duck> cmp = Comparator.comparing(Duck::getWeight)
.thenComparing(Duck::getAge)
.thenComparing(Duck::getName);
¡Hurra por lambdas, referencias de métodos y métodos predeterminados :)! Lástima que tengamos que definir getters, o usar lambdas explícitos , así:
Comparator<Duck> cmp = Comparator
.comparing((Duck duck)-> duck.weight)
.thenComparing((Duck duck)-> duck.age)
.thenComparing(duck-> duck.name);
La inferencia de tipo no funcionará con lambdas implícitas, por lo que debe especificar el tipo de argumento de las dos primeras lambdas. Más detalles en esta respuesta por Brian Goetz .
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
.
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();
}
});