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?
3 Réponses :
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();
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 ()
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);
vous pouvez simplifier cela avec Map :: compareByKey
btw
@Eugene merci beaucoup
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); } } });