22 April 2010

Building a map from a list by executing functions on its elements

Valid since: op4j 1.0

Description
Build a map by iterating the elements of a list and executing two functions on each of them: one for obtaining the key of the corresponding entry, and another one for obtaining the value.

Scenario
Our Country class looks like this:
public class Country {
    private final String name;
    private final Integer population;
    ...
    public String getName() {
        return this.name;
    }
    public Integer getPopulation() {
        return this.population;
    }
}
And we have a List<Country> countries variable containing data about some countries:
// countries == LIST [
//                     COUNTRY [ "Spain"; 45989016 ],
//                     COUNTRY [ "France"; 65447374 ],
//                     COUNTRY [ "Portugal"; 10707924 ],
//                     COUNTRY [ "United Kingdom"; 62041708 ],
//                     COUNTRY [ "Ireland"; 4459300 ],
//                   ]
...which we want to transform into a Map<String,Integer> containing the population of each country, indexed by the country name:
// populationByCountry == MAP [
//                              {"Spain": 45989016},
//                              {"France": 65447374},
//                              {"Portugal": 10707924},
//                              {"United Kingdom": 62041708},
//                              {"Ireland": 4459300},
//                            ]

Recipe
The toMap(keyFunction, valueFunction) action, applied on the list, will let us specify two functions: one for obtaining map keys out of the original list elements, and another one for obtaining their corresponding values. We will use getters for both:

Map<String,Integer> populationByCountry =
    Op.on(countries).
        toMap(Get.attrOfString("name"), Get.attrOfInteger("population")).get();

Comments
And what if we wanted the result map to be ordered by population, from the highest to the lowest figure? Something like:
// populationByCountry == MAP [
//                              {"France": 65447374},
//                              {"United Kingdom": 62041708},
//                              {"Spain": 45989016},
//                              {"Portugal": 10707924},
//                              {"Ireland": 4459300},
//                            ]
We could solve it in two ways:
  1. First sorting the list by population, then creating the map.
  2. First creating the map, then sorting its entries by value.
Let's try ordering the list first:
// INCORRECT: Order will be ascending!
Map<String,Integer> populationByCountry =
    Op.on(countries).
        sortBy(Get.attrOfInteger("population")).
        toMap(Get.attrOfString("name"), Get.attrOfInteger("population")).get();
There is a problem there: sortBy will use the specified function for sorting, but sorting will be made according to natural order, and this will mean that less-populated countries will come first (opposite to what we want). So we will have to reverse the list once sorted:
Map<String,Integer> populationByCountry =
    Op.on(countries).
        sortBy(Get.attrOfInteger("population")).reverse().
        toMap(Get.attrOfString("name"), Get.attrOfInteger("population")).get();
Now it is correct. Finally, let's try the create map first, then sort approach. This time we will have to sort the map by its entry values...
Map<String,Integer> populationByCountry =
    Op.on(countries).
        toMap(Get.attrOfString("name"), Get.attrOfInteger("population")).
        sortBy(Get.attrOfInteger("value")).reverse().get();

No comments:

Post a Comment