How to get all objects having the same max value with Java streams?
You could collect to a TreeMap
first and only get the last entry (where the max is)
players.stream()
.collect(Collectors.groupingBy(
Player::getPoints,
TreeMap::new,
Collectors.toList()
))
.lastEntry()
.getValue();
First group by the points and get a Map result,then find the max key in map. Time cost will be O(n):
List<Player> players = new ArrayList<>();
players.stream().collect(Collectors.groupingBy(Player::getPoints))
.entrySet().stream()
.max(Map.Entry.comparingByKey())
.ifPresent(System.out::println);
Here is a version that uses a custom collector. It is HUGE and ugly and complicated, but it runs in O(n), only makes one pass over the data, and requires little extra space.
List<Player> highest = players.stream().collect(ArrayList::new,
(list, player) -> {
if (list.isEmpty() || list.get(0).getPoints() == player.getPoints()) {
list.add(player);
} else if (list.get(0).getPoints() < player.getPoints()) {
list.clear();
list.add(player);
}
},
(l1, l2) -> {
if (l1.isEmpty()) {
l1.addAll(l2);
} else if (!l2.isEmpty()) {
int cmp = Integer.compare(l1.get(0).getPoints(), l2.get(0).getPoints());
if (cmp < 0) {
l1.clear();
l1.addAll(l2);
} else if (cmp == 0) {
l1.addAll(l2);
}
}
});
The proof that the accumulator and combiner are associative is left as an exercise to the reader.
EDIT: I tried to write a prettier combiner. I managed to write a shorter and weirder. I believe it is the same as the one above:
(l1, l2) -> {
int cmp = l1.stream().findAny().flatMap(p1 -> l2.stream().findAny().map(
p2 -> Integer.compare(p1.getPoints(), p2.getPoints()))).orElse(0);
if (cmp < 0) l1.clear();
if (cmp <= 0) l1.addAll(l2);
}