4
votes

Groupe de flux par plusieurs clés

Je souhaite utiliser des flux en java pour regrouper une longue liste d'objets basée sur plusieurs champs. Cela aboutira à une carte de carte de carte de carte de carte de .... de carte de listes.

Comment puis-je extraire uniquement des listes de ce flux complexe?

Voici un exemple de code pour démonstration (liste de chaînes, recherche de groupes de même longueur et première lettre). Je ne suis pas intéressé par les clés, mais uniquement par les entités groupées résultantes.

 [
     [A],
     [AA],
     [AAA, ABA],
     [B],
     [BB],
     [BBB, BAB],
     [C],
     [CC],
     [CCC, CAC]
 ]

Cela produira le résultat suivant

My Map =
{
    A =
    {
        1 = [A]
        2 = [AA]
        3 = [AAA, ABA]
    }
    B =
    {
        1 = [B]
        2 = [BB]
        3 = [BBB, BAB]
    }
    C =
    {
        1 = [C]
        2 = [CC]
        3 = [CCC, CAC]
    }
}

Quoi Ce qui m'intéresse en fait, ce ne sont que les listes des résultats ci-dessus et je veux le faire idéalement dans le cadre d'une opération groupby. Je sais que cela peut être fait par exemple en bouclant la structure des cartes résultantes. Mais y a-t-il un moyen d'y parvenir en utilisant des flux?

List<String> strings = ImmutableList.of("A", "AA", "AAA", "B", "BB", "BBB", "C", "CC", "CCC", "ABA", "BAB", "CAC");

Map<Character, Map<Integer, List<String>>> collect = strings.stream().collect(
        groupingBy(s -> s.charAt(0),
                groupingBy(String::length)
        )
);


0 commentaires

3 Réponses :


0
votes

Si vous voulez obtenir List> comme dans votre exemple, vous pouvez utiliser:

     ...
     .flatMap(e -> e.getValue().entrySet().stream())
     .flatMap(e -> e.getValue().stream())
     ...

Aussi si vous voulez obtenir une seule liste de chaînes, vous pouvez ajouter une autre opération flatMap :

List<List<String>> list = collect.entrySet().stream()
            .flatMap(e -> e.getValue().entrySet().stream())
            .map(Map.Entry::getValue)
            .collect(Collectors.toList());

Comme @John Bollinger l'a mentionné, utiliser un flux de valeurs, mais pas une entrée ne sera plus plus simple.


1 commentaires

Pourquoi diffuser le jeu d'entrées, puis mapper les entrées sur des valeurs au lieu de diffuser directement la collection de valeurs de la carte?



6
votes

Au lieu de créer des groupes imbriqués en utilisant des Collectors.groupingBy en cascade, vous devez regrouper par une clé composite:

List<List<String>> result = new ArrayList<>(map.values());

Ensuite, saisissez simplement les valeurs de la carte:

Map<List<Object>, List<String>> map = strings.stream()
        .collect(Collectors.groupingBy(s -> Arrays.asList(s.charAt(0), s.length())));

Si vous utilisez Java 9+, vous souhaiterez peut-être passer de Arrays.asList à List.of en créer les clés composites.

Cette approche fonctionne très bien pour votre cas parce que vous avez déclaré que vous n'étiez pas intéressé à conserver les clés, et parce que l'implémentation de List renvoyée par les deux Arrays.asList et List.of sont bien définis en termes de leurs méthodes equals et hashCode , c'est-à-dire qu'ils peuvent être utilisés en toute sécurité comme clés dans n'importe quelle Map.


2 commentaires

Merci, c'est essentiellement ce que je cherchais, j'ai en quelque sorte manqué qu'il existe une option pour la clé composée.


La List.of (…) du JDK9 nécessite un tableau. Vous auriez besoin de List.copyOf (…) de JDK10, mais compte tenu de l'exemple de code de la question, ImmutableList.copyOf (map.values ​​()) serait une option. Edit: Je vois, vous voulez dire la clé de la carte; pour la clé, soit List.of (s.charAt (0), s.length ()) ou ImmutableList.of (s.charAt (0), s.length ()) serait très bien.



0
votes

Je souhaite utiliser des flux en java pour regrouper une longue liste d'objets basée sur plusieurs champs.

C'est plus compliqué que votre exemple de code (invalide) ne me laisse penser. Néanmoins, vous pouvez aplatir un flux via sa méthode flatMap () correctement nommée. Pour un flux tel que celui que vous décrivez, vous devrez peut-être aplatir plusieurs fois ou définir une méthode de mappage personnalisée ou un lambda complexe pour aplatir jusqu'à ce que vous recherchez.

Dans le cas de une carte du formulaire présenté dans la question, vous pouvez faire quelque chose comme ceci:

List<List<String>> result = myMap.values().stream()
        .flatMap(m -> m.values().stream())  // as many of these as needed
        .collect(Collectors.toList());

0 commentaires