Comment grouper une liste à l'intérieur d'un objet par deux des attributs d'objet?
J'utilise Drools 7.9.0 et Java 8. J'ai une classe Result
qui est utilisée comme revenir à chaque règle Drools correspondante.
[ { name: "Whatever" id: 0001, occurrences: [{ (...) }, { (...) }] }, { name: "Whatever" id: 0002, occurrences: [{ (...) }] }, { name: "Other thing" id: 0002, occurrences: [{ (...) }] } ]
Après l'exécution de Drools, je me retrouve avec une List
. Converti en JSON, il ressemble à ceci.
RÉSULTAT ACTUEL:
[ { name: "Whatever" id: 0001, occurrences: [{ (...) }] }, { name: "Whatever" id: 0001, occurrences: [{ (...) }] }, { name: "Whatever" id: 0002, occurrences: [{ (...) }] }, { name: "Other thing" id: 0002, occurrences: [{ (...) }] } ]
Je dois regrouper ma liste de Résultat
pour que les occurrences
soient regroupées par id
et nom
, comme ceci.
RÉSULTAT ATTENDU:
import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.ArrayList; import java.util.List; @Data @NoArgsConstructor public class Result implements Serializable { private Integer id; private String name; private List<Occurrences> occurrences = new ArrayList<>(); public void addOccurrence(Occurrences occurrence) { this.occurrences.add(occurrence); } }
Quelle serait la meilleure façon de mettre cela en œuvre? J'ai deux options:
List
soit déjà structurée comme je le souhaite. Peut-être créer une classe avec une méthode addResult
qui vérifie la liste pour id
et nom
et ajouter l'occurrence à la bonne entrée. Mais ce n'est pas idéal, car cela augmentera la complexité des règles. List
afin qu'elle regroupe les occurrences
par id
et nom
. Mais je ne sais pas comment procéder de manière optimisée et simple. Quelle est la meilleure façon de faire la deuxième option?
4 Réponses :
Vous pouvez le faire comme ceci,
Map<String, List<Result>> resultsWithSameIdAndName = results.stream() .collect(Collectors.groupingBy(r -> r.getId() + ":" + r.getName())); List<Result> mergedResults = resultsWithSameIdAndName.entrySet().stream() .map(e -> new Result(Integer.valueOf(e.getKey().split(":")[0]), e.getKey().split(":")[1], e.getValue().stream().flatMap(r -> r.getOccurrences().stream()) .collect(Collectors.toList()))) .collect(Collectors.toList());
Un moyen simple de la 2ème option pourrait être:
Map<String, List<Occurences>> resultsMapped = new HashMap<>(); List<Result> collect = results.forEach(r -> resultsMapped.merge(String.format("%s%d", r.name, r.id), r.occurrences, (currentOccurences, newOccurences) -> { currentOccurences.addAll(newOccurences); return currentOccurences; });
Dans quelle carte stockez vos occurrences, la clé étant le nom et l'identifiant concaténé dans une chaîne. Cette clé n'est pas optimisée.
Vous pouvez également utiliser certains flux pour cela. Cela regroupera par ID et nom, puis créera un nouvel objet Result pour chaque groupe, contenant toutes les occurrences du groupe.
Collection<Result> processed = results.stream().collect(Collectors.groupingBy(r -> r.getId() + r.getName(), Collectors.collectingAndThen(Collectors.toList(), l -> new Result(l.get(0).getId(), l.get(0).getName(), l.stream().flatMap(c -> c.getOccurrences().stream()).collect(Collectors.toList())) ))).values();
Cela nécessite l'ajout d'un AllArgsConstructor à la classe Result, comme ainsi que d'autres getters, mais vous pouvez l'ajuster à votre guise. La clé utilisée pour le regroupement n'est pas sûre dans cet exemple.
J'ajouterais la méthode suivante à Résultat
:
List<Result> collect = results.stream () .collect (Collectors.groupingBy (Result::getId, Collectors.groupingBy (Result::getName, Collectors.toList ()))) .values () .stream () .map (Map::values) .flatMap (Collection::stream) .map (Collection::stream) .map (resultStream -> resultStream.reduce ((r0, r1) -> r0.addOccurrences (r1.getOccurrences ()))) .filter (Optional::isPresent) .map (Optional::get) .collect (Collectors.toList ());
Et puis vous pouvez utiliser Streams:
Result addOccurrences (List<Occurrences> occurrences) { this.occurrences.addAll (occurrences); return this; }