Reputation: 1879
Say I have a bunch of villains. They are characterized by how good, bad or ugly they are.
static class Villain {
String name;
int good;
int bad;
int ugly;
Villain(String name, int good, int bad, int ugly) {
this.name = name;
this.good = good;
this.bad = bad;
this.ugly = ugly;
}
}
Okay, meet the gang:
List<Villain> villains = new ArrayList<>();
villains.add(new Villain("Bob", 2, 2, 1));
villains.add(new Villain("Charley", 2, 1, 2));
villains.add(new Villain("Dave", 2, 1, 1));
villains.add(new Villain("Andy", 2, 2, 2));
villains.add(new Villain("Eddy", 1, 2, 2));
villains.add(new Villain("Franz", 1, 2, 1));
villains.add(new Villain("Guy", 1, 1, 2));
villains.add(new Villain("Harry", 1, 1, 1));
What I want to do is, I want to figure out who is the best, worst and ugliest. With that I mean to figure out who is the best. In case of a tie, who is the worst. In case of a tie, who is the ugliest.
I have succeeded in doing so, with the code below.
List<Villain> bestVillains = villains
.stream()
.collect(groupingBy(v -> v.good, TreeMap::new, toList()))
.lastEntry()
.getValue()
.stream()
.collect(groupingBy(v -> v.bad, TreeMap::new, toList()))
.lastEntry()
.getValue()
.stream()
.collect(groupingBy(v -> v.ugly, TreeMap::new, toList()))
.lastEntry()
.getValue();
This, indeed, result in a List<Villain>
with only one member: Andy. He truly is the best, worst and ugliest!
However, I have quite a bit of code repetition, collecting values, turning them into streams again, etc. Any suggestions on how te clean this up?
How is this being processed by the JVM. Sequentially or is there some magic going on underneath the hood?
Upvotes: 3
Views: 77
Reputation: 8386
If you are simply concerned about the code duplication, try to extract the code and reuse it, e.g.:
final BiFunction<List<Villain>, Function<Villain, Object>, List<Villain>> func = (listVillians, villianAttribute) -> listVillians
.stream()
.collect(groupingBy(villianAttribute, TreeMap::new, toList()))
.lastEntry()
.getValue();
And use it like so:
List<Villain> bestVillainsMK2 = func.apply(func.apply(func.apply(villains, Villain::getGood), Villain::getBad), Villain::getUgly);
Note: I added imaginary getters to the Villain
class.
The inner most call uses the original list, the others use the return of those functions.
Here as dedicated function
private static List<Villain> func2(List<Villain> listVillians, Function<Villain, Object> villianAttribute) {
return listVillians.stream()
.collect(groupingBy(villianAttribute, TreeMap::new, toList()))
.lastEntry()
.getValue();
}
Use is almost identical
List<Villain> bestVillainsMK3 = func2(func2(func2(villains, Villain::getGood), Villain::getBad), Villain::getUgly);
But if you are also interested in the right tool or pattern for your situation, see @nullpointers approach using comparators.
Upvotes: 1
Reputation: 31878
So the idea is that it first looks at which has the highest value for good, then (in case of a tie) which has the highest value for bad, then (if it is still not decisive) which has the highest value for 'ugly'
You are rather looking to sort using the following Comparator
for Villian
s :
Comparator<Villain> villainComparator = Comparator.comparingInt(Villain::getGood)
.thenComparingInt(Villain::getBad)
.thenComparingInt(Villain::getUgly);
Villain result = villains.stream()
.max(villainComparator)
.orElse(null);
Upvotes: 5
Reputation: 31
What you need is a Comparator. You can add this to your stream. It will look like this:
List<Villain> bestVillains = villains.stream()
.sorted((o1, o2) -> {
if(o2.good == o1.good){
if(o2.bad == o1.bad){
return o2.ugly - o1.ugly;
}else{
return o2.bad - o1.bad;
}
}else{
return o2.good - o1.good;
}
})
.limit(1)
.collect(Collectors.toList());
This produces a list of 1 Villain - the worst of the bunch. What happens here is the comparator only sorts in reverse and then you take the first entry.
Upvotes: 2
Reputation: 6290
You can use nested groupingBy
TreeMap<Integer, TreeMap<Integer, TreeMap<Integer, List<Villain>>>> collect =
villains.stream()
.collect(groupingBy(v -> v.good, TreeMap::new,
groupingBy(v -> v.bad, TreeMap::new,
groupingBy(v -> v.ugly, TreeMap::new, mapping(o -> o, toList())))));
Then print it:
System.out.println(collect.lastEntry().getValue()
.lastEntry().getValue()
.lastEntry().getValue());
Upvotes: 2