4
votes

Regrouper une liste à l'intérieur d'un objet par plusieurs attributs: Java 8

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:

  1. Modifiez les règles de Drools pour que ma 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.
  2. Post-traiter ma 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?


0 commentaires

4 Réponses :


3
votes

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


0 commentaires

2
votes

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.


0 commentaires

2
votes

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.


0 commentaires

2
votes

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


0 commentaires