18 April 2010

Grouping a list of objects by the value of one of their attributes

Valid since: op4j 1.0

Description
Group a list of objects by the value one of their attributes, effectively creating a Map<?,List<?>> containing the distinct values for that attribute as keys, and the objects returning the same value for that attribute in the corresponding value lists.

Scenario
Our City class looks like this:
public class City {
    private String country;
    private String name;
    ...
    public String getCountry() {
        return this.country;
    }
    public String getName() {
        return this.name;
    }
}
And we have a List<City> cities variable containing some City objects:
// cities == LIST [
//                  CITY [ country="Spain";name="Santiago" ],
//                  CITY [ country="France";name="Marseille" ],
//                  CITY [ country="Portugal";name="Porto" ],
//                  CITY [ country="Spain";name="Barcelona" ],
//                  CITY [ country="Portugal";name="Lisboa" ],
//                  CITY [ country="Portugal";name="Viseu" ]
//                ]
Which we want to group by country, so that we get a Map<String,List<City>> just like:
// citiesByCountry == 
//           MAP [
//                 {
//                   "Spain" : 
//                   LIST [
//                     CITY [ country="Spain";name="Santiago" ],
//                     CITY [ country="Spain";name="Barcelona" ]
//                   ]
//                 }
//                 {
//                   "France" : 
//                   LIST [
//                     CITY [ country="France";name="Marseille" ]
//                   ]
//                 }
//                 {
//                   "Portugal" : 
//                   LIST [
//                     CITY [ country="Portugal";name="Porto" ],
//                     CITY [ country="Portugal";name="Lisboa" ],
//                     CITY [ country="Portugal";name="Viseu" ]
//                   ]
//                 }
//               ]

Recipe
What we need to do is create a map from our list by zipping the map keys (our existing cities will be the values) and then grouping values with the same keys into lists.

So the action we will execute is zipAndGroupKeysBy(function), and the function we will use to obtain the map keys will be an attribute getter, provided by the Get function hub:

Let's see it in action:

Map<String,List<City>> citiesByCountry =
    Op.on(cities).zipAndGroupKeysBy(Get.attrOfString("country")).get();


Comments
Let's have a look at the equivalent non-op4j Java code:
Map<String,List<City>> citiesByCountry =
    new LinkedHashMap<String,List<City>>();
for (City city : cities) {
    List citiesForCountry = citiesByCountry.get(city.getCountry());
    if (citiesForCountry == null) {
        citiesForCountry = new ArrayList<City>();
        citiesByCountry.put(city.getCountry(), citiesForCountry);
    }
    citiesForCountry.add(city);
}

5 comments:

  1. Where can I find this 'Get' class? Is it included in 1.0-beta3?

    ReplyDelete
  2. "Get" is included in 1.0 stable, which is being released in this very moment ;-). You can already download the distribution from both sourceforge and the Maven central repositories. The web site will be updated in a couple of hours.

    ReplyDelete
  3. Nice! I've been looking at Op4j as a way to get some of the nice Groovy-style fluency going in a pure Java environment.
    Some nice work so far, please keep it up!

    ReplyDelete
  4. Hi!
    How about using google-collections? Same functionality will look like this:

    Multimap citiesByCountry = HashMultimap.create();
    for( City city : cities )
    {
    citiesByCountry.put( city.getCountry(), city );
    }

    There is a plenty of very usefull classes in this library.
    Maybe You could integrate somehow with google-collections classes to product example like this:

    Multimap citiesByCountry = Op.on( cities ).zipAndGroupKeysBy( Get.attrOfString( "country" ) ).get();

    ReplyDelete
  5. Agrrr.. ;) Eated acute brackets. Little update. Instead of just:

    Multimap

    there should be:

    Multimap<String, City>

    ReplyDelete