Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get all objects having the same max value with Java streams? [duplicate]

I have players which have points. I want to get all the players who share the max amount of points using a stream and a filter.

public class Player {
    private int points; // Getter omitted
}

I can do this by first getting the player with the most points, and the filtering all players that have the same amount.

Player topPlayer = players.stream().max(Comparator.comparing(Player::getPoints)).orElse(null);

players.stream().filter(p -> p.getPoints() == topPlayer.getPoints()).collect(Collectors.toList());

Can this be done with a single predicate / single line ?

like image 840
Yotus Avatar asked Jan 24 '19 12:01

Yotus


Video Answer


3 Answers

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();
like image 106
Eugene Avatar answered Sep 28 '22 07:09

Eugene


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);
like image 29
TongChen Avatar answered Sep 28 '22 07:09

TongChen


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);
}
like image 35
Lii Avatar answered Sep 28 '22 06:09

Lii