6
votes

Comment obtenir tous les objets ayant la même valeur maximale avec les flux Java?

J'ai des joueurs qui ont des points. Je veux obtenir tous les joueurs qui partagent le maximum de points en utilisant un flux et un filtre.

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

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

Je peux le faire en obtenant d'abord le joueur avec le plus de points, et en filtrant tout joueurs qui ont le même montant.

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

Cela peut-il être fait avec un seul prédicat / une seule ligne?


0 commentaires

3 Réponses :


9
votes

Vous pouvez d'abord collecter dans une TreeMap et n'obtenir que la dernière entrée (où le maximum est)

players.stream()
       .collect(Collectors.groupingBy(
           Player::getPoints,
           TreeMap::new,
           Collectors.toList()
       ))
       .lastEntry()
       .getValue();


8 commentaires

Bien que la solution originale de l'OP soit plus efficace en termes de complexité (O (n) vs votre O (nlogn)).


@Eran était d'accord. aussi l'espace supplémentaire ...


C'est peut-être moins efficace, mais je pourrais faire bon usage de cette TreeMap, car je dois répéter mon code pour chaque groupe de points, en fait.


J'ai juste essayé votre solution, mais getLastEntry () n'est pas public dans TreeMap, n'est-ce pas? Ou est-ce que je manque quelque chose?


@Yotus mon mauvais, c'est en fait lastEntry


Vous n'avez pas besoin de passer Collectors.toList () .


@shmosel je pense que vous faites


Vous pouvez le faire dans O (n): players.stream () .collect (Collectors.collectingAndThen (Collectors.groupingBy (‌ Player :: getPoints), m -> Collections.max (m.entrySet (), Map.Entry.comparingByKey ()))) .getValue ()



5
votes

Commencez par regrouper les points et obtenez un résultat sur la carte, puis trouvez la clé max dans la carte. Le coût en temps sera O (n):

List<Player> players = new ArrayList<>();
players.stream().collect(Collectors.groupingBy(Player::getPoints))
        .entrySet().stream()
        .max(Map.Entry.comparingByKey())
        .ifPresent(System.out::println);


2 commentaires

vous pouvez simplifier cela avec Map :: compareByKey btw


@Eugene merci beaucoup



4
votes

Voici une version qui utilise un collecteur personnalisé. C'est ÉNORME, laid et compliqué, mais il fonctionne en O (n) , ne fait qu'un seul passage sur les données et nécessite peu d'espace supplémentaire.

(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);
}

Le la preuve que l'accumulateur et le combineur sont associatifs est laissée comme exercice au lecteur.


EDIT: J'ai essayé d'écrire un plus joli combineur. J'ai réussi à écrire un texte plus court et plus étrange. Je pense que c'est le même que celui ci-dessus:

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);
            }
        }
    });


0 commentaires