Reputation: 6323
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:
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;
}
}
Upvotes: 3
Views: 148
Reputation: 34470
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.
Upvotes: 1
Reputation: 46181
Since you were also looking for a non-stream solution:
public static Map<Integer, List<Person>> group(List<Person> people) {
Map<Integer, List<Person>> intermediateGrouping = new HashMap<>();
for (Person person : people) {
intermediateGrouping.computeIfAbsent(person.getAge(), k -> new ArrayList<>()).add(person);
}
Comparator<Entry<Integer, List<Person>>> byGroupSize = Entry.comparingByValue(Comparator.comparingInt(List::size));
Comparator<Entry<Integer, List<Person>>> byAge = Entry.comparingByKey();
List<Entry<Integer, List<Person>>> entries = new ArrayList<>(intermediateGrouping.entrySet());
entries.sort(byGroupSize.reversed().thenComparing(byAge.reversed()));
Map<Integer, List<Person>> result = new LinkedHashMap<>(entries.size());
for (Entry<Integer, List<Person>> entry : entries) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
Or if you prefer the result to be a List<List<Person>>
:
public static List<List<Person>> group(List<Person> people) {
Map<Integer, List<Person>> intermediateGrouping = new HashMap<>();
for (Person person : people) {
intermediateGrouping.computeIfAbsent(person.getAge(), k -> new ArrayList<>()).add(person);
}
Comparator<Entry<Integer, List<Person>>> byGroupSize = Entry.comparingByValue(Comparator.comparingInt(List::size));
Comparator<Entry<Integer, List<Person>>> byAge = Entry.comparingByKey();
List<Entry<Integer, List<Person>>> entries = new ArrayList<>(intermediateGrouping.entrySet());
entries.sort(byGroupSize.reversed().thenComparing(byAge.reversed()));
List<List<Person>> result = new ArrayList<>(entries.size());
for (Entry<Integer, List<Person>> entry : entries) {
result.add(entry.getValue());
}
return result;
}
Upvotes: 1
Reputation: 100
Try modifiying your sort comparator using the below implementation when the sizes are equal for a no streams approach
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)
{
if(o1.getValue().size()<o2.getValue().size())
return 1;
else if(o1.getValue().size()>o2.getValue().size())
return -1;
else {
if( o1.getKey()< o2.getKey())
return 1;
else if(o1.getKey()>o2.getKey())
return -1;
else
return 0;
}
}
});
Let me know if it works on all your Test Cases
Upvotes: 0
Reputation: 140504
Use streams.
First, group them by age:
Map<Integer, List<Person>> groupedByAge =
people.stream().collect(groupingBy(Person::getAge));
Then sort the entries of this map:
Comparator<Map.Entry<Integer, List<Person>>> byCount = comparingInt(e -> e.getValue().size());
Comparator<Map.Entry<Integer, List<Person>>> byAge = comparingInt(Map.Entry::getKey);
Stream<Map.Entry<Integer, List<Person>>> sorted =
groupedByAge.entrySet().stream().sorted(byCount.reversed().thenComparing(byAge.reversed()));
Then just get the list out of there:
List<List<Person>> result = sorted.map(Map.Entry::getValue).collect(toList());
(You can put this all into a single expression, but I claim it is more readable broken out like this).
Upvotes: 3