Je voudrais obtenir le groupe de scores le plus élevé par ID. Si les deux scores les plus élevés sont identiques, je voudrais obtenir le score le plus élevé en fonction de l'ID facultatif le plus bas. Je voudrais l'obtenir dans Java Stream. les codes suivants qui ne fonctionnent pas Exemple:
Array List:
ID: 1 Score: 80 FacultatifId: 1
ID: 1 Score: 90 FacultatifId: 2
ID: 1 Score: 90 FacultatifId: 3
ID: 2 Score: 80 FacultatifId: 1
ID: 2 Score: 100 FacultatifId: 3
ID: 2 Score: 100 OptionalId: 5
Le résultat devrait être
ID: 1 Score 90 OptionalId: 2
ID 2 Score 100 OptionnelId: 3
Map<Long, Optional<Person>> result1 = records.stream() .collect(Collectors.groupingBy(Person::getId, Collectors.maxBy(Comparator.comparing(Person::getScore)), Collector.minBy(Comparator.comparing(Person::getOptionalId)))); for(Person ns: result1) { sb.append(ns.getBatchNumber()); sb.append(',');
4 Réponses :
J'ai un peu changé et j'ai introduit une classe d'assistance qui compare le score et le optionalId.
public class T21Group { public static void main(String[] args) { List<Person> records = new ArrayList<>(); records.add(new Person(1, 80, 1)); records.add(new Person(1, 90, 2)); records.add(new Person(1, 90, 3)); records.add(new Person(2, 80, 1)); records.add(new Person(2, 100, 3)); records.add(new Person(2, 100, 5)); Map<Long, Optional<Person>> result1 = records.stream() .collect(Collectors.groupingBy(Person::getId, Collectors.maxBy(Comparator.comparing(Pair::new)))); for (Optional<Person> ns : result1.values()) { System.out.println(ns); } } public static class Pair implements Comparable<Pair> { long score; long optionalId; public Pair(Person p) { score = p.getScore(); optionalId = p.getOptionalId(); } @Override public int compareTo(Pair o) { if (this.score == o.score) { return Long.compare(o.optionalId, this.optionalId); } return Long.compare(this.score, o.score); } } public static class Person { private long id; private long score; private long optionalId; public Person(long id, long score, long optionalId) { this.id = id; this.score = score; this.optionalId = optionalId; } @Override public String toString() { return "ID: " + id + " Score: " + score + " OptionalId: " + optionalId; } public long getId() { return id; } public void setId(long id) { this.id = id; } public long getScore() { return score; } public void setScore(long score) { this.score = score; } public long getOptionalId() { return optionalId; } public void setOptionalId(long optionalId) { this.optionalId = optionalId; } } }
Vous ne vérifiez jamais la valeur facultative en cas d'égalité.
Grrrr. Eh bien, il ne devrait pas être difficile de changer le comparateur pour utiliser également cette valeur. Je regarde.
Vous pouvez essayer le code de flux suivant qui regroupe par ID
, puis trouver le score maximum en utilisant un tri à deux niveaux, d'abord par score, puis par ID facultatif en cas d'égalité pour le score :
import static java.util.Collections.reverseOrder; import static java.util.Comparator.comparing; Map<Long, Optional<Person>> result1 = records.stream() .collect(Collectors.groupingBy(Person::getId, Collectors.maxBy( Comparator.comparing(Person::getScore) .thenComparing(reverseOrder(comparing(Person::getOptionalId)))))); Optional[ID: 1 Score: 90 OptionalId: 2] Optional[ID: 2 Score: 100 OptionalId: 3]
L'astuce ici est d'inverser l'ordre de tri pour uniquement l'ID facultatif, que nous voulons être croissant et non décroissant. L'ordre de tri par défaut serait décroissant, car nous invoquons Collections.maxBy
.
Je cite cette excellente question SO pour obtenir de l'aide sur la syntaxe inverse. De plus, j'ai emprunté le code de la plaque chauffante à @mayamar pour configurer la démo suivante:
(démo uniquement à des fins de démonstration)
Merci pour vos précieuses suggestions, je l'apprécie vraiment.
@TimBiegeleisen cela ne fonctionnerait que si vous supposez que OptionalId
est vraiment un long
, mais cela pourrait être un java.util.Optional
...
@Eugene Je n'interprète pas l'utilisation du mot "optionnel" par l'OP pour faire référence à la classe Optional
, mais plutôt au fait que l'ID secondaire est juste une donnée facultative qui peut être ou non utilisé pour briser une cravate.
Je vous suggère de commencer par un Comparateur
qui priorise un max de score
puis un min de optionalId
. C'est un bon moyen de le transmettre à une variable par souci de concision:
Collection<Person> filtered = records.stream() // Stream<Person> .collect(Collectors.groupingBy( // from Map<Long, List<Person>> Person::getId, Collectors.collectingAndThen( // .. downstream to .. Collectors.reducing((a, b) -> // .. Map<Long, Optional<Person>> comparator.compare(a, b) > 0 ? a : b), Optional::get)) // .. Map<Long, Person> .values(); // .. Collection<Person>
Maintenant, utilisez des collecteurs en utilisant java-stream .
Collectors :: groupingBy
pour regrouper toutes les Person
s par id
et en aval la valeur Collectors :: reduction
pour réduire toutes les Person
s avec le même id
en une seule utilisant le Comparateur
pour obtenir celui avec le score
le plus élevé et le plus bas optionalId
. Collectors :: CollectAndThen
pour aplatir la structure de Collection>
à Collection
puisque toute opération de réduction se traduit par Facultatif
- cette étape est facultative et peut être ignorée en fonction de votre exemple. Voici le code:
final Comparator<Person> comparator = Comparator .comparing(Person::getScore) // descending score first .thenComparing(Comparator // then .. .comparing(Person::getOptionalId) // .. optionalId .reversed()); // .. but ascending
[Person [id = 1, score = 90, optionalId = 2], Person [id = 2, score = 100, optionalId = 3]]
Merci pour vos précieuses suggestions, je l'apprécie vraiment.
Pour une valeur Id donnée, il doit y avoir une personne. L'existence de la valeur Id dépend uniquement de la personne. Donc, s'il existe un identifiant, il doit y avoir aussi une personne. Alors quel est l'intérêt d'avoir un Optional
comme valeur de la carte. En revanche, il est plus logique d'avoir simplement une instance de Person
comme valeur dans la map
. Ici, j'utilise le collecteur toMap
de concert avec le BinaryOperator.maxBy
pour faire le travail. Voici à quoi ça ressemble. Notez comment BinaryOperator.maxBy
est utilisé comme mergeFunction
.
{1=Person [id=1, score=90, optionalId=2], 2=Person [id=2, score=100, optionalId=3]}
Et voici la sortie pour l'entrée donnée ci-dessus. P >
Map<Integer, Person> maxPersonById = records.stream() .collect(Collectors.toMap(Person::getId, Function.identity(), BinaryOperator.maxBy(Comparator.comparing(Person::getScore) .thenComparing(Comparator.comparing(Person::getOptionalId).reversed()))));
C'est la bonne réponse, +1 pour afficher Collectors.toMap
avec BinaryOperator.maxBy
, qui est la meilleure approche ici.
Qu'est-il arrivé aux scores avec 80?
Le score de 80 doit être éliminé.Je ne prendrai que le score le plus élevé de chaque groupe.De l'exemple ci-dessus, 80 est le plus bas et il y a deux 90 qui sont les plus élevés.Je ne prendrai qu'un 90 entre deux plus élevés, selon celui qui a le plus petit ID optionnel. le max par prend le plus haut au hasard de deux 90 dont je ne veux pas
@TimBiegeleisen: Disparu. Notez le
Collectors.maxBy
appliqué au score.OptionalId
est vraiment unjava.util.Optional
? ou unint / long
?