I went through all the manuals out there and all SO questions but still unable to figure this out…
I have a List (integer represents age):
List<Person> people = Arrays.asList ( new Person("bob", 10), new Person("sue", 4), new Person("tom", 37), new Person("jim", 10), new Person("boo", 4), new Person("ekk", 53), new Person("joe", 10) );
I need to:
- group the list by age,
- sort by group sizes (descending),
- sort by age (descending)
So using the example above the result would have to be like this:
{10=[bob, jim, joe],4=[sue, boo], 53=[ekk], 37=[tom]}
What I tried:
I tried with and without streams. I failed on both.
Note: I would lean toward no stream solution, because from my testing of the below code it seems like streams are much slower (I used System.nanotime()). These 3 operations will be done thousands of times each time, so it may make a slight difference.
Using streams here is what I did:
List<List<Person>> grpd = new ArrayList<> ( people.stream() .collect ( groupingBy(Person::getAge, toList()) ) .values() ); grpd = grpd.stream().sorted((a, b) -> Integer.compare(b.size(), a.size())).collect(toList());
No streams approach:
Map<Integer, List<Person>> grouped = new HashMap<>(); for (Person person : people) { if (grouped.containsKey(person._age)) { grouped.get(person._age).add(person); } else { List<Person> p = new ArrayList<>(); p.add(person); grouped.put(person._age, p); } } List<Map.Entry<Integer, List<Person>>> entries = new ArrayList<>(grouped.entrySet()); Collections.sort(entries, new Comparator<Map.Entry<Integer, List<Person>>>() { @Override public int compare(Map.Entry<Integer, List<Person>> o1, Map.Entry<Integer, List<Person>> o2) { return Integer.compare(o2.getValue().size(), o1.getValue().size()); } }); Map<Integer, List<Person>> sortedBySize = new LinkedHashMap<>(); for (Map.Entry<Integer, List<Person>> entry : entries) { sortedBySize.put(entry.getKey(), entry.getValue()); }
Problem: I have no idea how to add the final sort on either case.
public class Person { public String _name; public int _age; public int getAge() { return _age; } public Person(String name, int age) { _name = name; _age = age; } @Override public String toString() { return _name; } }
Advertisement
Answer
As you’ve also asked about a non-stream solution, here it is:
Map<Integer, List<Person>> grouped = new HashMap<>(); people.forEach(person -> grouped.computeIfAbsent( person.getAge(), k -> new ArrayList<>()) .add(person));
This groups by age. Now let’s sort the entries, first by group size descending, then by age descending:
List<Map.Entry<Integer, List<Person>>> toSort = new ArrayList<>(grouped.entrySet()); toSort.sort( Comparator.comparingInt((Map.Entry<Integer, List<Person>> e) -> e.getValue().size()) .reversed() .thenComparingInt(Map.Entry.comparingByKey().reversed()));
Now, toSort
is a sorted list of entries. You need to put those entries into a new map:
Map<Integer, List<Person>> sorted = new LinkedHashMap<>(); toSort.forEach(e -> sorted.put(e.getKey(), e.getValue()));
And sorted
holds the result you want.