Dynamic Sorting Utility

Hi Friends !!!

Recently I came across a requirement where in I was supposed to sort a Custom Class based on multiple parameters.

Problem Statement :You are supposed to sort a Student class with parameters rollNo ,
name,age and weight. Now you want to sort it on name, if names match sort it on 
rollNo ,if rollNo matches sort it on age and so on. To add spice to the problem : 
 The priority order may vary.

If the priority of ordering was fixed and pre-defined the implementation of this shouldn’t be a very difficult task. What I needed was a functionality where in your code can dynamically take in the priority of sort-able fields and work out the magic.

So hold on to your seats and have patience , since this is going to be long but interesting !!! 🙂

To start with let us break our problem. I would do it as –

  • step 1 : Code for a simple Comparator with multiple sort-able fields, but priority is predefined.
  • step 2 : Sort out some way to pass the newly introduced dynamic priority data to your comparator.
  • step 3 : Replace your code written in step 1 and rather than comparing in fixed order, compare while iterating through the data you just passed on in step 2.

 

Confused !!!!

Don’t be… we will get into details 🙂 (It has nothing to do with our site’s name)

Firstly our Student class.

public class NewStudent{
	private Integer rollNo;
	private String name;
	private Integer age;
	private Float weight;
	public NewStudent(int rollNo,String name,int age,float wt){
		this.rollNo = rollNo;
		this.name = name;
		this.age = age;
		this.weight = wt;
	}
	// Getters and Setters follow

}

Now lets start with our steps:

Say we start with an example  – rollNo -> weight

A very generic approach would be to write a Comparator and override the compare (Student s1, Student s2) method as

public int compare(NewStudent student1, NewStudent student2) { 
//Use your Own Class .. Student is Mine
  if(student1.getRollNo().compareTo(student2.getRollNo())==0){
       return student1.getWeight().compareTo(student2.getWeight());
  }
  return student1.getRollNo().compareTo(student2.getRollNo());
}

Now our second step :

I used a TreeMap for that TreeMap<Integer, SortParameter> . You can decide to have your own way of passing this information on. This TreeMap would inform our comparator the order in which the fields are to be compared.

If your are wondering what this SortParameter is !!! So it is simple an enum which has got the sort-able fields mapping to the actual field name in my class. I would actually use it to invoke its getters using reflection to fetch values. This might sound complicated but believe me it isn’t. Soon you will get to know. As of now assume it is some data that I am passing to my Comparator.

enum SortParameter {
    ROLLNO("rollNo"),NAME("name"),WEIGHT("weight");
    private final String value;

    SortParameter(String value){
  		 this.value=value;
  	 }
  	 public String toValue() {
  			return value;
  	}
}

Enum would actually let the user know the exact fields on which the sorting can be done (and restrict them at the same time to those particular fields only).

Time for Step 3:
I have done a small addition to ensure pre-calculation of getters to be invoked.We can statically map the field names to their getters and at runtime; just invoke them to get the values

static{
 try {
  info=Introspector.getBeanInfo(NewStudent.class);
  getters = new HashMap<String, Method>();
  for(SortParameter sortParam : EnumSet.allOf(SortParameter.class))  {
   for (PropertyDescriptor pd : info.getPropertyDescriptors()) {  
     String name = pd.getName(); 
     if(!sortParam.toValue().equalsIgnoreCase(name)){
	    continue;
     }
     Method getter = pd.getReadMethod();
     getters.put(name, getter);
   }
 }
}
 catch (IntrospectionException e) {
    e.printStackTrace();
 } 
}

Now we are all set to write the Complete Comparator’s compare(NewStudent student1, NewStudent student2) implementation –

public int compare(NewStudent student1, NewStudent student2) { 
    List<Integer> checkedParams = new ArrayList<Integer>();
    return customComaparator(student1, student2,checkedParams);
  }

private int customComaparator(NewStudent student1, NewStudent stu               dent2, List<Integer> checkedParams) {

  for(Map.Entry<Integer,SortParameter> entry:parameters.entrySet())  {
   if(checkedParams.contains(entry.getKey()))
 	continue;
   String val = entry.getValue().toValue();
   try {
      Comparable comaparable = (Comparable)getters.get(val).invoke(                                          student1);
      Comparable comparable2 = (Comparable)getters.get(val).invoke(                                          student2);
      if(comaparable.compareTo(comparable2)==0){
	    checkedParams.add(entry.getKey());
 	    //to ensure the second param is checked for comparison if the first value is equal
	    return customComaparator(student1,student2,checkedParams);
      }
      return comaparable.compareTo(comparable2);
   } catch (Exception e) {
	e.printStackTrace();
   }
 }
return 0;
}

My Comparator’s Constructor would be :

public class StudentComparator implements Comparator<NewStudent> {
    private TreeMap<Integer,SortParameter> parameters;
    private static BeanInfo info;
    private static HashMap<String,Method> getters;
    public StudentComparator(TreeMap<Integer,SortParameter> params)   {
	this.parameters = params;
   }
}

And that’s it. I won’t say this is the best /only way of doing the stuff. But probably might give my readers some pointers in case they are facing a similar problem statement.

Happy Coding !!!!

Leave a Reply

Your email address will not be published. Required fields are marked *