Groovier way of sorting over multiple fields in a list of maps in groovy
Recently I was working on a Groovy and Grails Development project and had a requirement to sort multiple fields in a list of maps.
I needed to sort the below 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.
[java]
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 ]
]
[/java]
One way to achieve this is to use spaceship operator as follows :
[java]
list.sort { a,b ->
a.firstName <=> b.firstName ?: a.lastName <=> b.lastName ?: a.age <=> b.age
}*.id
[/java]
which gives the correct output as a list of id’s = [2, 5, 0, 1, 3, 4]
One cool 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 my colleague Bhagwat for helping me to arrive at this solution)
[java]
String padAge(Integer age){
return String.format("%03d",age)
}
[/java]
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 :
[java]
list.sort{it.firstName+it.lastName+padAge(it.age)}*.id
[/java]
and output will be [2, 5, 0, 1, 3, 4]
I Hope it will be helpful.
Pingback: Sorting Map inside Groovy , Javascript | workdiaryyy
It Worked Nicely. Thanks
Came up with an alternative, adding a new sort method to the Collection metaclass: https://gist.github.com/3314416