Sorting Java objects using multiple keys

JavaSortingComparatorComparable

Java Problem Overview


I've got a collection of Duck objects and I'd like to sort them using multiple keys.

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

eg. I want to sort them primarily by their weights, and secondarily by their age. If two ducks have the exact same weight and the exact same age, then let's differentiate them using their names as a tertiary key. I might do something like this:

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

Well I do this quite frequently, but this solution doesn't smell right. It doesn't scale well, and it's easy to mess up. Surely there must be a better way of sorting Ducks using multiple keys! Does anybody know of a better solution?

EDIT removed unnecessary else branches

Java Solutions


Solution 1 - Java

Guava is more elegant:

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

Apache commons-lang has a similar construct, CompareToBuilder.

Solution 2 - Java

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

Solution 3 - Java

Java 8 solution:

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

Hooray for lambdas, method references, and default methods:)! Too bad we have to define getters, or use explicit lambdas, like so:

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

Type inference won't work with implicit lambdas, so you have to specify the argument type of the first two lambdas. More details in this answer by Brian Goetz.

Solution 4 - Java

Firstly, your solution isn't that slow.

If you really want another method, then give each duck a "score" which is essentially a single number that is the sum of their three characteristics, but with a huge weighting (excuse the almost unavoidable pun) for weight, a lesser one for age; and a very small one for the name.

You can allocate ~10 bits for each characteristic, so for each characteristic you have to be in the range 0..1023.

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

This is probably completely unneeded, but whatever :)

Solution 5 - Java

You can use the CompareToBuilder from Apache Commons Lang. (It explains comparable, but works for Comparator too).

Solution 6 - Java

You can use chained BeanComparators from 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

Solution 7 - Java

I have just rewritten your code without nested else statements. Do you like it now?

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionandrasView Question on Stackoverflow
Solution 1 - JavaJB NizetView Answer on Stackoverflow
Solution 2 - JavaBoris PavlovićView Answer on Stackoverflow
Solution 3 - JavaandrasView Answer on Stackoverflow
Solution 4 - JavaNoxvilleView Answer on Stackoverflow
Solution 5 - JavaThirlerView Answer on Stackoverflow
Solution 6 - JavaEmmanuel BourgView Answer on Stackoverflow
Solution 7 - JavaAlexRView Answer on Stackoverflow