2
votes

Obtenez le meilleur score de ArrayList en utilisant Java Stream

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 commentaires

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 un java.util.Optional ? ou un int / long ?


4 Réponses :


0
votes

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

}

}


2 commentaires

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.



1
votes

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

(démo uniquement à des fins de démonstration)


3 commentaires

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.



3
votes

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 .

  • 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]]


1 commentaires

Merci pour vos précieuses suggestions, je l'apprécie vraiment.



2
votes

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


1 commentaires

C'est la bonne réponse, +1 pour afficher Collectors.toMap avec BinaryOperator.maxBy , qui est la meilleure approche ici.