开发者

Sorting over multiple fields

开发者 https://www.devze.com 2023-03-29 04:13 出处:网络
I am trying to sort first by car make then sort by car year then sort by car mileage. All the fields are strings.

I am trying to sort first by car make then sort by car year then sort by car mileage. All the fields are strings.

Here's what I've tried so far:

class Sorter implements Comparator<Car> {
  @Override
  public int compare(Car o1, Car o2) {
    if(o1.getMake().compareToIgnoreCase(o2.getMake()) == 0 && Integer.parseInt(o1.getYear()) != Integer.parseInt(o2.getYear())){
      if(Integer.parseInt(o1.getYear()) > Integer.parseInt(o2.getYear())){
        return -1;
      }else{
        return 1;
      }
    }
    if(o1.getMake().compareToIgnoreCase(o2.getMake()) == 0 && Integer.parseInt(o1.getYear()) == Integer.parseInt(o2.getYear())){
      if(Integer.parseInt(o1.getMileage()) > Integer.parseInt(o2.ge开发者_StackOverflow中文版tMileage())){
        return 1;
      }else{
        return -1;
      }
    }
    return o1.getMake().compareToIgnoreCase(o2.getMake());

  }
}

I'm trying to build an algorithm like you would have in excel where you can order by one column then another then another.


The general approach is called lexicographic ordering, which is what we use to sort words. Assuming that we want to sort two objects of class C by fields f1, f2 ... fn we proceed as follows:

  1. Compare the two f1 fields; if they are different the result of the comparison is the final result, otherwise
  2. Repeat for each of the following fields.

In code (beware - not compiled):

class Sorter implements Comparator<Car> {
  @Override
  public int compare(Car o1, Car o2) {
    int res = o1.getMake().compareToIgnoreCase(o2.getMake());
    if ( res != 0 )
      return res;
    res = o1.getYear().compareTo(o2.getYear());
    if ( res != 0 )
      return res;
    return Integer.parseInt(o1.getMileage()).compareTo(Integer.parseInt(o2.getMileage()));
  }
}


If a class has multiple significant fields, the order in which you compare them is critical. You must start with the most significant field and work your way down. If a comparison results in anything other than zero (which represents equality), you’re done; just return the result. If the most significant fields are equal, go on to compare the next-most-significant fields, and so on. If all fields are equal, the objects are equal; return zero.. (Effective Java).

public int compare(Car car1, Car car2) {
    // compare make 
    int makeDiff = car1.getMake().compare(car2.getMake());
    if ( makeDiff != 0 )
        return makeDiff;

    // compare year
    int yearDiff = car1.getYear().compare(car2.getYear());
    if ( yearDiff != 0 )
        return yearDiff;

    // compare mileage
    int mileageDiff = car1.getMileage().compare(car2.getMileage());
    if ( mileageDiff != 0 )
        return mileageDiff;

    return 0;
}


I'd write a MultiComparator like the following:

class MultiComparator<T> implements Comparator<T> {
  private final List<Comparator<T>> comparators = new ArrayList<Comparator<T>>();
  public MultiComparator(final List<Comparator<T>> comps) { ... }
  @Override int compareTo(T t1, T t2) {
    for (Comparator<T> c: comparators) {
      int d = c.compareTo(t1, t2);
      if (d != 0) return d;
    }
    return 0;
  }
}

Then you instantiate a MultiComparator<T> with 3 independent comparators, each comparing only one of the fields you said. Then you will get the result you want.


I am trying to sort first by car make then sort by car year then sort by car mileage. All the fields are strings.

A) You should change your design. Year should be an int, mileage should be int, float or an object consisting of one of these and a Unit. Strings are just not the correct representation. Also, if you use them in a Comparator, you will have to convert them to the proper type at least once per comparison, which is horribly inefficient,

b) For implementing Comparators you'd probably want to use an external library. Both Guava and Commons / Lang are pretty good at that. In the following example I'll assume that both Year and Mileage are numeric.

Guava (ComparisonChain):

public int compare(Car o1, Car o2) {
    return ComparisonChain.start()
       .compare(o1.getYear(), o2.getYear())
       .compare(o1.getMileage(), o2.getMileage())
       .compare(o1.getMake(), o2.getMake())
       .getResult();
}

Commons / Lang (CompareToBuilder):

public int compare(Car o1, Car o2) {
    return new CompareToBuilder()
       .append(o1.getYear(), o2.getYear())
       .append(o1.getMileage(), o2.getMileage())
       .append(o1.getMake(), o2.getMake())
       .toComparison();
}

As you can see both versions are very similar and also a lot easier than coding that yourself.


Have a look at the ComparatorChain from the Apache Commons Collection.
At the following site I have a tutorial: Sorting Objects By Multiple Attributes

0

精彩评论

暂无评论...
验证码 换一张
取 消