sorting « Intelligrape Groovy & Grails Blogs

Posts Tagged ‘ sorting ’

Groovier way of sorting over multiple fields in a list of maps in groovy

Posted by on August 10th, 2012
List list = [
    [id:0, firstName: 'Sachin', lastName: 'Tendulkar', age: 40 ],
    [id:1, firstName: 'Sachin', lastName: 'Tendulkar', age: 103 ],
    [id:2, firstName: 'Ajay', lastName: 'Tendulkar', age: 48 ],
    [id:3, firstName: 'Virendra', lastName: 'Sehwag', age: 5 ],  
    [id:4, firstName: 'Virendra', lastName: 'Sehwag', age: 50 ],  
    [id:5, firstName: 'Sachin', lastName: 'Nayyar', age: 15 ]   
]

I needed to sort the above list of maps by firstName, then by lastName and then by ascending age. It means first, the firstNames will be compared. If they are same, lastNames will be compared and even if they are same, ages will be compared to decide order.

One way to achieve this is to use spaceship operator as follows :

 
list.sort { a,b -> 
   a.firstName <=> b.firstName ?: a.lastName <=> b.lastName ?: a.age <=> b.age 
}*.id

which gives the correct output as a list of id’s = [2, 5, 0, 1, 3, 4]

One cool(and groovier) way of achieving the same result is to concatenate firstName,lastName and age fields as a single string and then use the resulted string in sorting. However, there will be a problem with sorting age in this manner. Among two ages, 40 and 103, groovy will treat 103 as lower than 40, because sorting is done in ascii collating sequence and in that 1 of 103 comes before 4 of 40.

One way to rectify this problem is to left-pad the age with sufficient number of 0′s before it is compared. Thus, 40 will become 040 and 103 will remain as it is and we will get the correct ordering.(Special thanks to Bhagwat ji for helping me to arrive at this solution)

String padAge(Integer age){
    return String.format("%03d",age)
}

In format “%03d”, 0 specifies the filler digit and 3 specifies the maximum number of digits after padding. Thus, our final one-liner to perform sorting over multiple fields becomes :

list.sort{it.firstName+it.lastName+padAge(it.age)}*.id

and output will be [2, 5, 0, 1, 3, 4]

It was quite helpful for me. Hope it will be helpful for you too.

Raj Gupta
raj.gupta@intelligrape.com

Posted in Grails, Groovy

Alphanumeric Sorting using Criteria Query (with MySQL database)

Posted by on February 2nd, 2012

I am working on a Grails application with MySQL database. I had a use case in which I had to implement alphanumeric sorting using Criteria Query on Grails. By alphanumeric sorting I mean if there is a class Employee with field empId then on doing :

 
Employee e1 = new Employee(empId:'emp10').save()
Employee e2 = new Employee(empId:'emp2').save()
Employee e3 = new Employee(empId:'emp1').save()
Employee e4 = new Employee(empId:'1emp').save()
Employee e5 = new Employee(empId:'10emp').save()
Employee e6 = new Employee(empId:'2emp').save()

Employee.createCriteria().list([sort:'empId'])
            /* OR */
Employee.createCriteria().list{
    order('empId')
}

should give result like [e4,e6,e5,e3,e2,e1]. But according to sql sorting the result would be [e5,e4,e6,e3,e1,e2]

One thing to note was that criteria query implemented sorting at the database end.
After googling around, I came across a script that had a SQL function which converted the field value to string that can alphanumerically sorted easily at database end.

To migrate the functions in script to database you have to unzip the script and write following command on terminal:

mysql -u username -p database_name < /path/to/unzipped/directory/natsort.install.mysql

Now only thing I had to do was to get a derived field from the Grails property that had to be sorted alphanumerically which was easily possible as the Grails had ability to create transient field using formula (thanks to wonderful Blog by Uday). Thus I had to create a new transient field using SQL function in formula. So I did something like this :

class Employee {
    String empId
    String empIdSortField           //transient field
    static mapping = {
        empIdSortField formula: 'natsort_canon(empId, "natural")'
    }
}

Now if I had to get Employee objects are sorted by empId alphanumerically, what I would do is :

Employee.createCriteria().list {
    order('empIdSortField')
}

Hope this was helpful to you!

Posted in Database, GORM, Grails

Sorting in javascript

Posted by on March 13th, 2011

Recently in my project I needed to sort an object list on the basis of object name. For the same purpose I created the following function


function sortList(objList) {
 objList.sort(sortByName);
}

function sortByName(a, b) {
 var x = a.name;
 var y = b.name;
 return ((x < y) ? -1 : ((x > y) ? 1 : 0));
}

This function sorts an object list on the basis of name of object. Hope it helps

Sachin Anand

sachin[at]intelligrape[dot]com

Groovy: Sort list of objects on the basis of more than one field

Posted by on January 25th, 2011

Usually, when we deal with databases, we don’t face such kind of situation because we query database to get result-set in required order.

But let’s say you have a List of objects that you want to sort on the basis of two fields. Let’s discuss it with an example.


I have a list of Tasks (with date and priority fields). I want to sort these tasks ordered by Date and priority-wise (as second level sorting).

Here, I gonna use Expando class, so I can directly run this in my groovyConsole. But definitely you can use some ‘Task’ class.

Expando a = new Expando(date: Date.parse('yyyy-MM-dd','2011-01-01'), priority:1)
Expando b = new Expando(date: Date.parse('yyyy-MM-dd','2011-01-01'), priority:2)
Expando c = new Expando(date: Date.parse('yyyy-MM-dd','2011-01-02'), priority:1)
Expando d = new Expando(date: Date.parse('yyyy-MM-dd','2011-01-01'), priority:3)

def list = [a,d,c,b]

If sorting was required on the basis of date only, it was very simple.

list.sort(it.date)

But as per our requirements – order by date (first level sorting) and priority (second level sorting). We can do something like following.

list.sort{x,y->
  if(x.date == y.date){
    x.priority <=> y.priority
  }else{
    x.date <=> y.date
  }
}

Well, there could be some better way. If you know please put your comments. But it worked well in my case.



Cheers!
Salil Kalia
Salil [at] IntelliGrape [dot] com
Twitter LinkedIn