1

I have to sort a collection of person objects by Birthdate so I created the following pojo.

    public class Person implements Comparable<Person> {

    private long id;
    private String name;
    private Date birthDate;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Date getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(Date birthDate) {
        this.birthDate = birthDate;
    }

    @Override
    public int compareTo(Person person) {
        if (getBirthDate() == null || person.getBirthDate() == null)
            return 0;
        return getBirthDate().compareTo(person.getBirthDate());
    }
}

This does the job for sorting the birth dates but the additional requirement I have is to sort them by which birth date is closest to today ascending. So for example, if today is October 11, then the birthdays would be sorted as an example.

October 20th
November 5th
January 3rd...

In the way I currently have it since the year is part of the comparable the dates are showing from the oldest year to most recent year.

How can I sort these birthdates without the year?

Byron
  • 3,833
  • 9
  • 34
  • 39
  • I am sure there are some better ways but you could always add a getMonth() and getDay() – buzzsawddog Oct 11 '13 at 20:53
  • 2
    There's a number of ways I can think of doing this, one of the simplest would be to use a `Calendar` and compare the `DAY_OF_YEAR` values... – MadProgrammer Oct 11 '13 at 20:53
  • In addition look at the answer in this question http://stackoverflow.com/questions/5927109/sort-objects-in-arraylist-by-date – buzzsawddog Oct 11 '13 at 20:55

3 Answers3

5

This does the job for sorting the birth dates but the additional requirement I have is to sort them by which birth date is closest to today ascending. So for example, if today is October 11, then the birthdays would be sorted as an example

If i understood you right, while sorting you are not considering the year e.g., 1975 or is it?. it seems to me that you are after a Birth Day remembering functionality. However, If you are sorting based upon month and day, Following comparedTo() function should give you some idea to compare based upon month and day excluding year:

  class Person implements Comparable<Person>
  {
        Date birthDay;
        static SimpleDateFormat formatter = new SimpleDateFormat("MMM dd, yyyy");

        public Person(String birthDay) throws ParseException
       {
             this.birthDay = formatter.parse(birthDay); 

        }        

        @Override
        public int compareTo(Person o) {
        Calendar cal1 = Calendar.getInstance();
        cal1.setTime(this.birthDay);
        Calendar cal2 = Calendar.getInstance();
        cal2.setTime(o.birthDay);

        int month1 = cal1.get(Calendar.MONTH); 
        int month2 = cal2.get(Calendar.MONTH);

        if(month1 < month2)  
          return -1;
        else if(month1 == month2)
          return cal1.get(Calendar.DAY_OF_MONTH) - cal2.get(Calendar.DAY_OF_MONTH);

        else return 1;

      }
}

Trying with following code Test:

List<Person>persons = new ArrayList<>();

 persons.add(new Person("MAR 2, 2001"));
 persons.add(new Person("JAN 7, 1972"));
 persons.add(new Person("JAN 2, 1976"));
 persons.add(new Person("MAR 4, 1985"));

 Collections.sort(persons);

 for(Person p : persons)
   System.out.println(p.formatter.format(p.birthDay));
                 //Person.formatter is SimpleDateFormat with format "MMM dd, yyyy" 
                 // in person class, i declared it as static

Output:

Jan 02, 1976
Jan 07, 1972
Mar 02, 2001
Mar 04, 1985

Getting the list sorted according to a pivot e.g, OCT 11

I think after sorting you just could round the list, thinking it is circular. For example suppose the sorted list:

JAN 20, FEB 5, SEP 18, OCT 9, OCT 20, NOV 23

If our pivot is OCT 11 choosing the immediate larger(smallest date larger than pivot) date to it, would be OCT 20. You can find it just using a for loop. Now, we just need to round it thinking it is circular:

OCT 20, NOV 23 --> JAN 20, FEB 5, SEP 18, OCT 9

Formally, find the index i of immediate larger date comparing to our pivot based upon month and day(try using the compareTo example), then create a new list, insert the element starting from the index i to n-1 and then 0 to i-1, here n is the size of the Birth Date list.

I hope that helps.

Sage
  • 15,290
  • 3
  • 33
  • 38
  • if the pivot point is OCT 11, shouldn't OCT 14 be the next immediate larger date? – Byron Oct 12 '13 at 16:38
  • yes. sorry i missed that. ;) but you caught my drift right ? fixed it. replacing `OCT 14` with `OCT 9` – Sage Oct 12 '13 at 16:46
  • did the solution work for you ? Silence always frightens me. :( – Sage Oct 12 '13 at 22:59
  • yes the solution worked for me. I wish I could give both of you credit for the answer as I ended up using a combination of both your answer & MadProgrammer. I added an additional attribute to the person object "dayOfYear" and made that my comparator. I then followed your suggestion above of creating a new list and inserting into that list from the location of immediate larger number. – Byron Oct 13 '13 at 02:38
  • uhh, i just wanted to make sure that what i am proposing was fruitful or not. :) – Sage Oct 13 '13 at 02:40
2

Write a Comparator

You can specify a comparator for Persons, which include the year, and another which excludes it.

You will need to sort your List, by calling:

Collections.sort(list, new BirthdayWithoutYearComparatr());
Christian Kuetbach
  • 15,850
  • 5
  • 43
  • 79
1

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 Comparators 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...

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • There is something tells me that he is actually not worrying about the year while sorting. he is after an app functionality like *Birth day remember*. :) – Sage Oct 12 '13 at 01:51
  • @sage I may be wrong, but as I read it, the OP was after sorting the day/month based on its distance from a given date (ie today), without using the year part, which, the custom sort does – MadProgrammer Oct 12 '13 at 02:11
  • I didn't say you are wrong, you are right. But his given example says me that he might not be actually caring for year part. Say he does care for year. still, isn't it much simpler if we sort it based upon only Month and Day, and then rotate. I am the one who would be wrong! – Sage Oct 12 '13 at 02:18