Groovier way of sorting over multiple fields in a list of maps in groovy « Intelligrape Groovy & Grails Blogs

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

Posted by
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

This entry was posted on August 10th, 2012 at 5:42 pm and is filed under Grails, Groovy . You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

One Response to “Groovier way of sorting over multiple fields in a list of maps in groovy”

  1. Tim Yates says:

    Came up with an alternative, adding a new sort method to the Collection metaclass: https://gist.github.com/3314416

Leave a Reply