You have a compound series of problems.
First, I would not, personally, make the Person
class Comparable
, the main problem (I see with this), is that it's not really comparing the Person
object, but just one of it's properties. This locks you in to a comparison that might not meet all of your requirements all of the time.
I would, personally, instead, set up a series of Comparator
s that did very specific jobs, for example, comparing the birth date of the Person
.
The next problem is the comparable API doesn't support mismatched parameters, okay, it does, but it's messy...
What we need is some way that we can compare a object (or more specifically, a property of an object) with some other value.
Now, because the core API is based around comparing like values, we need to provide our own implementation to support our requirements.
So, the below, is a quick sort implementation that allows you to provide a List
of objects a ISortMatcher
and a value to be compared with (this is the base value that all the values in the list will be compared to)
import java.util.Collections;
import java.util.List;
public class QuickSort {
public static <O, M> void sort(List<O> values, M value, ISortMatcher<O, M> matcher) {
sort(values, value, matcher, 0, values.size() - 1);
}
protected static <O, M> void sort(List<O> values, M value, ISortMatcher<O, M> matcher, int low, int high) {
int i = low, j = high;
// Get the pivot element from the middle of the list
int pivot = matcher.compare(values.get(low + (high - low) / 2), value);
// Divide into two lists
while (i <= j) {
// If the current value from the left list is smaller then the pivot
// element then get the next element from the left list
while (matcher.compare(values.get(i), value) < pivot) {
i++;
}
// If the current value from the right list is larger then the pivot
// element then get the next element from the right list
while (matcher.compare(values.get(j), value) > pivot) {
j--;
}
// If we have found a values in the left list which is larger then
// the pivot element and if we have found a value in the right list
// which is smaller then the pivot element then we exchange the
// values.
// As we are done we can increase i and j
if (i <= j) {
Collections.swap(values, i, j);
i++;
j--;
}
}
// Recursion
if (low < j) {
sort(values, value, matcher, low, j);
}
if (i < high) {
sort(values, value, matcher, i, high);
}
}
public static interface ISortMatcher<O, M> {
public int compare(O o, M m);
}
}
Okay, this is a very specific requirement for the sort and acts a little like a binary search, but it will do...
What this allows us to do is define the base value we want to match, the algorithm by which the comparison is carried out with and the list of values to be sorted. It works on a similar concept to the core API, returning values less then, equal to or greater then 0 to indicate the weight of the result.
Now, we can start comparing...
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
public class SortDates {
public static final SimpleDateFormat SDF = new SimpleDateFormat("dd/MM/yyyy");
public static void main(String[] args) {
new SortDates();
}
public SortDates() {
List<Person> people = new ArrayList<Person>(5);
for (int index = 0; index < 10; index++) {
Date date = getDate(
(int)(Math.random() * 30) + 1,
(int)(Math.random() * 12),
(int)(Math.random() * 113) + 1900);
people.add(new Person(date));
}
Collections.sort(people, new DateOfBirthComparator());
System.out.println("By date of birth");
for (Person p : people) {
System.out.println(p);
}
Collections.shuffle(people);
System.out.println("");
System.out.println("Shuffled");
for (Person p : people) {
System.out.println(p);
}
QuickSort.ISortMatcher matcher = new QuickSort.ISortMatcher<Person, Date>() {
@Override
public int compare(Person o, Date m) {
Calendar with = Calendar.getInstance();
with.setTime(m);
Calendar to = Calendar.getInstance();
to.setTime(o.getBirthDate());
to.set(Calendar.YEAR, with.get(Calendar.YEAR));
int withDOY = with.get(Calendar.DAY_OF_YEAR);
int toDOY = to.get(Calendar.DAY_OF_YEAR);
int result = 0;
if (withDOY < toDOY) {
result = toDOY - withDOY;
} else if (withDOY > toDOY) {
result = withDOY - toDOY;
}
return result;
}
};
QuickSort.sort(people, new Date(), matcher);
System.out.println("");
System.out.println("To today (" + SDF.format(new Date()) + ")");
for (Person p : people) {
System.out.println(p);
}
}
public Date getDate(int day, int month, int year) {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.DATE, day);
cal.set(Calendar.MONTH, month);
cal.set(Calendar.YEAR, year);
return cal.getTime();
}
public class Person {
private Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
}
@Override
public String toString() {
return SDF.format(birthDate);
}
public Date getBirthDate() {
return birthDate;
}
}
public class DateOfBirthComparator implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
return o1.getBirthDate().compareTo(o2.getBirthDate());
}
}
}
At the heart of the example, is the QuickSort
Matcher
. This determines the weight to be applied between a given value and the value we want to use as the base for the comparison.
QuickSort.ISortMatcher matcher = new QuickSort.ISortMatcher<Person, Date>() {
@Override
public int compare(Person o, Date m) {
Calendar with = Calendar.getInstance();
with.setTime(m);
Calendar to = Calendar.getInstance();
to.setTime(o.getBirthDate());
to.set(Calendar.YEAR, with.get(Calendar.YEAR));
int withDOY = with.get(Calendar.DAY_OF_YEAR);
int toDOY = to.get(Calendar.DAY_OF_YEAR);
int result = 0;
if (withDOY < toDOY) {
result = toDOY - withDOY;
} else if (withDOY > toDOY) {
result = withDOY - toDOY;
}
return result;
}
};
Now, I've taken you requirement literally, this will sort the values based on their distance from the given Date
, so you may end up with dates before the specified date appearing after dates after the specified date...
For example,
By date of birth
05/10/1905
01/10/1906
13/03/1921
11/04/1942
07/12/1944
27/04/1953
05/07/1988
15/12/1988
19/03/1995
12/07/2001
Shuffled
13/03/1921
01/10/1906
05/07/1988
12/07/2001
11/04/1942
27/04/1953
19/03/1995
15/12/1988
07/12/1944
05/10/1905
To today (12/10/2013)
05/10/1905
01/10/1906
07/12/1944
15/12/1988
12/07/2001
05/07/1988
27/04/1953
11/04/1942
19/03/1995
13/03/1921
You can see that 05/10
appears before 07/12
as it is closer to the target date...