Reputation: 219
I have been trying to understand how sorting using Comparable works. I have class Activity implementing Comparable interface. I am trying to understand how activity.finish-this.finish; sorts in descending order and how this.finish-activity.finish sorts in ascending order. I am confused about what to use first for sorting in ascending and descending?
class Activity implements Comparable {
int start;
int finish;
public Activity(int start, int finish) {
this.start = start;
this.finish = finish;
}
@Override
public int compareTo(Object o) {
Activity activity = (Activity) o;
return activity.finish-this.finish;
}
}
public class ActivitySelectionProblem {
public static void main(String[] args) {
int start[] = {1, 3, 0, 5, 8, 5};
int finish[] = {2, 4, 10, 7, 9, 9};
List<Activity> list = new ArrayList<>();
for(int i = 0 ; i < start.length ; i++){
list.add(new Activity(start[i], finish[i]));
}
Collections.sort(list);
}
}
Upvotes: 3
Views: 1211
Reputation: 339053
The Answer by Elliott Frisch is correct. In addition, consider using Comparator
rather than Comparable
for your situation.
Collections.sort( // Utility method for sorting a `List`.
activitiesAscending , // A list copied from your original list of inputs.
Comparator.comparingInt( Activity :: finish ) // A predefined implementation of `Comparator` interface, built for comparing `int` values.
); // Results in the passed list being modified, being sorted according to the logic of the passed `Comparator`.
Collections.sort(
activitiesDescending ,
Collections.reverseOrder( // Reverses the sorting logic of the passed `Comparator` object.
Comparator.comparingInt( Activity :: finish ) // Same `Comparator` as seen above.
)
);
compareTo
consistent with equals
An additional problem is that if you read the Javadoc for Comparable
, you will see that generally it is best for the logic of compareTo
to be be constant with the logic of equals
.
But you want your comparing to be done only on the finish
member field. So you should write your equals
(and hashCode
) to also focus solely on finish
. But that would mean that your last two objects seen in the Question would be considered equal as they both share the value 9
for their finish
. I expect you do not consider those two objects to be equal.
Comparator
Defining a Comparator
outside your class is likely more appropriate than making your Activity
class implement Comparable
. We do just that in the rest of this Answer.
With Java 16, we can define your Activity
class as a record. The compiler implicitly creates the constructor, getters, equals
& hashCode
, and toString
. Using record
is not critical to this Answer, but makes the example code brief.
package work.basil.example;
public record Activity( int start , int finish )
{
}
Comparator.comparingInt
Rather than define your own implementation of the Comparator
interface, we can use the predefined comparator for comparing int
values.
We use a method reference using the double COLON characters to name the method that should be used in retrieving an int from our objects being compared. That method reference is Activity :: finish
. In records the default getter method is named the same as the property name, without the get
prefix commonly seen à la JavaBeans. So simply finish
as method name, not getFinish
.
Comparator.comparingInt( Activity :: finish ) ) // Method reference from `Activity` being passed to `Comparator` built to compare `int` values.
Let's define your inputs using convenient List.of
syntax.
List < Activity > activities =
List.of(
new Activity( 1 , 2 ) ,
new Activity( 3 , 4 ) ,
new Activity( 0 , 10 ) ,
new Activity( 5 , 7 ) ,
new Activity( 8 , 9 ) ,
new Activity( 5 , 9 )
);
The list produced by that code is unmodifiable. We want to sort a list, so we need a modifiable list. Feed that unmodifiable list to the constructor of a modifiable implementation of List
such as ArrayList
.
List < Activity > activitiesAscending = new ArrayList <>( activities );
Ask the utility method Collections.sort
to sort that new list. Pass a Comparator
as we showed earlier.
Collections.sort( activitiesAscending , Comparator.comparingInt( Activity :: finish ) );
For the opposite, in descending order with your 9
finishing objects first and your 2
finishing object last, we again call Collections.sort
. But we pass a different Comparator
. We pass our original comparingInt
comparator to the utility method Collections.reverseOrder
to produce a new comparator.
List < Activity > activitiesDescending = new ArrayList <>( activities );
Collections.sort( activitiesDescending , Collections.reverseOrder( Comparator.comparingInt( Activity :: finish ) ) );
Entire code example.
List < Activity > activities =
List.of(
new Activity( 1 , 2 ) ,
new Activity( 3 , 4 ) ,
new Activity( 0 , 10 ) ,
new Activity( 5 , 7 ) ,
new Activity( 8 , 9 ) ,
new Activity( 5 , 9 )
);
List < Activity > activitiesAscending = new ArrayList <>( activities );
Collections.sort( activitiesAscending , Comparator.comparingInt( Activity :: finish ) );
List < Activity > activitiesDescending = new ArrayList <>( activities );
Collections.sort( activitiesDescending , Collections.reverseOrder( Comparator.comparingInt( Activity :: finish ) ) );
System.out.println( "activities = " + activities );
System.out.println( "activitiesAscending = " + activitiesAscending );
System.out.println( "activitiesDescending = " + activitiesDescending );
When run.
activities = [Activity[start=1, finish=2], Activity[start=3, finish=4], Activity[start=0, finish=10], Activity[start=5, finish=7], Activity[start=8, finish=9], Activity[start=5, finish=9]]
activitiesAscending = [Activity[start=1, finish=2], Activity[start=3, finish=4], Activity[start=5, finish=7], Activity[start=8, finish=9], Activity[start=5, finish=9], Activity[start=0, finish=10]]
activitiesDescending = [Activity[start=0, finish=10], Activity[start=8, finish=9], Activity[start=5, finish=9], Activity[start=5, finish=7], Activity[start=3, finish=4], Activity[start=1, finish=2]]
Upvotes: 0
Reputation: 201477
It's hacky. The contract for compareTo
is that it returns less than 0
when the result of comparison is less, greater than 0
when greater and 0
when equal. It is not obvious what the code does. Instead, I would use Integer.compare(int, int)
- something like
@Override
public int compareTo(Object o) {
return Integer.compare(this.finish, ((Activity) o).finish);
}
Upvotes: 3