Sunday, February 7, 2010

Groovy: Grouping by Multiple Properties

In a recent Groovy program, I had a need to group a list of objects by multiple properties. To give an example, take the following class:

class Person {
def firstName
def lastName
def city
def hobby

String toString() {
"Person[city=$city, hobby=$hobby, firstName=$firstName, lastName=$lastName]"
}
}

I wanted to group things by city and hobby so people can find others nearby who have similar interests. Grouping by one property of the class is easy enough using the groupBy method. Here I'd receive a map, keyed by each found hobby:

def people = [
new Person(city:"Queens", hobby:"Scrabble", firstName:"Frank", lastName:"Costanza"),
new Person(city:"Queens", hobby:"Scrabble", firstName:"Estelle", lastName:"Costanza"),
new Person(city:"Brooklyn", hobby:"Scrabble", firstName:"David", lastName:"Puddy"),
new Person(city:"Brooklyn", hobby:"Scrabble", firstName:"Lloyd", lastName:"Braun"),
new Person(city:"Brooklyn", hobby:"Architecture", firstName:"Art", lastName:"Vandelay"),
]

def map = people.groupBy { it.hobby }
println "Number of groups: ${groups.size()}"
println groups.each { hobby, group -> println "$hobby: ${group.size()}" }

This gives me two groups, one for "Scrabble", and one for "Architecture":

  Number of groups: 2
Scrabble: 4
Architecture: 1

A method to add the city property to the grouping did not seem so apparent to me. I did come up with one method that feels rather hacky, so I'll share it here and see if anybody can helpfully suggest a better approach.

Here I'll make a unique key, by combining the city and hobby properties, separated by a hyphen. I have no need for the key used for grouping here after the grouping is done, so I just grab the values of the created map, which should be a list of Person lists:

groups = people.groupBy { "${it.city}-${it.hobby}" }.values()
println "Number of groups: ${groups.size()}"

groups.each { group ->
println "Group:"
group.each { person ->
println "\t$person"
}
}

Which gives me the 3 groups I was looking for:

  Number of groups: 3
Group:
Person[city=Queens, hobby=Scrabble, firstName=Frank, lastName=Costanza]
Person[city=Queens, hobby=Scrabble, firstName=Estelle, lastName=Costanza]
Group:
Person[city=Brooklyn, hobby=Scrabble, firstName=David, lastName=Puddy]
Person[city=Brooklyn, hobby=Scrabble, firstName=Lloyd, lastName=Braun]
Group:
Person[city=Brooklyn, hobby=Architecture, firstName=Art, lastName=Vandelay]

Here I could also consider popluating a list of Group objects with unique Group object with city and hobby properties that could also hold a Person list.

1 comment: