ulquiorra February 2016

Group objects in list by multiple fields

I have a simple object like this

public class Person{

 private int id;
 private int age;
 private String hobby;

 //getters, setters

}

I want to group a list of Person by attributes

Output should be like this

Person count/Age/Hobby
2/18/Basket
5/20/football

With a chart for more understanding

chart

X axis : hobby repartition Y axis : count of person distribution

colors represents age

I managed to group by one attribute using map, but I can't figure how to group by multiples attributes

//group only by age . I want to group by hobby too
 personMapGroupped = new LinkedHashMap<String, List<Person>>();
 for (Person person : listPerson) {
            String key = person.getAge();
            if (personMapGroupped.get(key) == null) {
                personMapGroupped.put(key, new ArrayList<Person>());
            }
            personMapGroupped.get(key).add(person);
        }

Then I retrieve the groupable object like this

  for (Map.Entry<String, List<Person>> entry : personMapGroupped .entrySet()) {

            String key = entry.getKey();// group by age
            String value = entry.getValue(); // person count
            // i want to retieve the group by hobby here too... 
        }

Any advice would be appreciated. Thank you very much

Answers


Austin D February 2016

Implement methods for comparing people according to the different fields. For instance, if you want to group by age, add this method to Person:

public static Comparator<Person> getAgeComparator(){
    return new Comparator<Person>() {

        @Override
        public int compare(Person o1, Person o2) {
            return o1.age-o2.age;
        }
    };
}

Then you can simply call: Arrays.sort(people,Person.getAgeComparator()) or use the following code to sort a Collection:

List<Person> people = new ArrayList<>();
people.sort(Person.getAgeComparator());

To sort using more than one Comparator simultaneously, you first define a Comparator for each field (e.g. one for age and one for names). Then you can combine them using a ComparatorChain. You would use the ComparatorChain as follows:

ComparatorChain chain = new ComparatorChain();
chain.addComparator(Person.getNameComparator());
chain.addComparator(Person.getAgeComparator());


LordAnomander February 2016

You could simply combine the attributes to a key.

for (Person person : listPerson) {
    String key = person.getAge() + ";" + person.getHobby();
    if (!personMapGrouped.contains(key)) {
       personMapGrouped.put(key, new ArrayList<Person>());
    }
    personMapGrouped.get(key).add(person);
}

The count of entries is easy to determine by using personMapGrouped.get("18;Football").getSize().


Thomas February 2016

I'm not sure about your requirements, but I'd probably use multiple maps (Google Guava's Multimap would make that easier btw) and sets, e.g. something like this:

//I'm using a HashMultimap since order of persons doesn't seem to be relevant and I want to prevent duplicates   
Multimap<Integer, Person> personsByAge = HashMultimap.create();

//I'm using the hobby name here for simplicity, it's probably better to use some enum or Hobby object
Multimap<String, Person> personsByHobby = HashMultimap.create();

//fill the maps here by looping over the persons and adding them (no need to create the value sets manually

Since I use value sets Person needs a reasonable implementation of equals() and hashCode() which might make use of the id field. This also will help in querying.

Building subsets would be quite easy:

Set<Person> age18 = personsByAge.get(18);
Set<Person> basketballers = personsByHobby.get( "basketball" );

//making use of Guava again
Set<Person> basketballersAged18 = Sets.intersection( age18, basketballers );

Note that I made use of Google Guava here but you can achieve the same with some additional manual code (e.g. using Map<String, Set<Person>> and manually creating the value sets as well as using the Set.retainAll() method).

Post Status

Asked in February 2016
Viewed 2,013 times
Voted 14
Answered 3 times

Search




Leave an answer