web-dev-qa-db-fra.com

Fractionner la liste en sous-listes le long des éléments

J'ai cette liste (List<String>):

["a", "b", null, "c", null, "d", "e"]

Et j'aimerais quelque chose comme ça:

[["a", "b"], ["c"], ["d", "e"]]

En d'autres termes, je souhaite fractionner ma liste en sous-listes en utilisant la valeur null comme séparateur afin d'obtenir une liste de listes (List<List<String>>). Je cherche une solution Java 8. J'ai essayé avec Collectors.partitioningBy mais je ne suis pas sûr que c'est ce que je recherche. Merci!

64
Oneiros

La seule solution que je trouve pour le moment consiste à mettre en place votre propre collecteur personnalisé. 

Avant de lire la solution, je souhaite ajouter quelques notes à ce sujet. J'ai pris cette question davantage comme un exercice de programmation, je ne suis pas sûr que cela puisse être fait avec un flux parallèle. 

Vous devez donc être conscient du fait que silencieusement sera interrompu si le pipeline est exécuté dans parallel

Ceci est pas un comportement souhaitable et devrait être évité. C'est pourquoi je lève une exception dans la partie du combinateur (au lieu de (l1, l2) -> {l1.addAll(l2); return l1;}), utilisée en parallèle lors de la combinaison des deux listes, de sorte que vous ayez une exception au lieu d'un résultat incorrect.

En outre, cela n’est pas très efficace en raison de la copie de la liste (bien qu’elle utilise une méthode native pour copier le tableau sous-jacent).

Alors, voici l'implémentation du collecteur:

private static Collector<String, List<List<String>>, List<List<String>>> splitBySeparator(Predicate<String> sep) {
    final List<String> current = new ArrayList<>();
    return Collector.of(() -> new ArrayList<List<String>>(),
        (l, elem) -> {
            if (sep.test(elem)) {
                l.add(new ArrayList<>(current));
                current.clear();
            }
            else {
                current.add(elem);
            }
        },
        (l1, l2) -> {
            throw new RuntimeException("Should not run this in parallel");
        },
        l -> {
            if (current.size() != 0) {
                l.add(current);
                return l;
            }
        );
}

et comment l'utiliser:

List<List<String>> ll = list.stream().collect(splitBySeparator(Objects::isNull));

Sortie:

[[a, b], [c], [d, e]]


Comme la réponse de Joop Eggen est sortie , il semble que cela puisse être fait en parallèle (remerciez-le pour cela!). Avec cela, l’implémentation de collecteur personnalisé est réduite à:

private static Collector<String, List<List<String>>, List<List<String>>> splitBySeparator(Predicate<String> sep) {
    return Collector.of(() -> new ArrayList<List<String>>(Arrays.asList(new ArrayList<>())),
                        (l, elem) -> {if(sep.test(elem)){l.add(new ArrayList<>());} else l.get(l.size()-1).add(elem);},
                        (l1, l2) -> {l1.get(l1.size() - 1).addAll(l2.remove(0)); l1.addAll(l2); return l1;});
}

qui a laissé le paragraphe sur le parallélisme un peu obsolète, mais je le laisse faire car cela peut être un bon rappel.


Notez que l’API Stream n’est pas toujours un substitut. Il existe des tâches plus faciles et mieux adaptées à l’utilisation des flux, et d’autres non. Dans votre cas, vous pouvez également créer une méthode utilitaire pour cela:

private static <T> List<List<T>> splitBySeparator(List<T> list, Predicate<? super T> predicate) {
    final List<List<T>> finalList = new ArrayList<>();
    int fromIndex = 0;
    int toIndex = 0;
    for(T elem : list) {
        if(predicate.test(elem)) {
            finalList.add(list.subList(fromIndex, toIndex));
            fromIndex = toIndex + 1;
        }
        toIndex++;
    }
    if(fromIndex != toIndex) {
        finalList.add(list.subList(fromIndex, toIndex));
    }
    return finalList;
}

et appelez-le comme List<List<String>> list = splitBySeparator(originalList, Objects::isNull);

Il peut être amélioré pour vérifier les cas Edge. 

31
Alexis C.

Bien qu'il y ait déjà plusieurs réponses et une réponse acceptée, il manque encore quelques points à ce sujet. Premièrement, le consensus semble être que résoudre ce problème en utilisant des flux n'est qu'un exercice et que l'approche conventionnelle en boucle est préférable. Deuxièmement, les réponses données jusqu’à présent ont négligé une approche utilisant des techniques de tableaux ou de vecteurs qui, selon moi, améliore considérablement la solution de flux.

Premièrement, voici une solution conventionnelle, aux fins de discussion et d’analyse:

static List<List<String>> splitConventional(List<String> input) {
    List<List<String>> result = new ArrayList<>();
    int prev = 0;

    for (int cur = 0; cur < input.size(); cur++) {
        if (input.get(cur) == null) {
            result.add(input.subList(prev, cur));
            prev = cur + 1;
        }
    }
    result.add(input.subList(prev, input.size()));

    return result;
}

C'est la plupart du temps simple mais il y a un peu de subtilité. Un point important est qu'une sous-liste en attente de prev à cur est toujours ouverte. Lorsque nous rencontrons null, nous le fermons, l'ajoutons à la liste des résultats et avancons prev. Après la boucle, nous fermons la sous-liste de manière inconditionnelle.

Une autre observation est qu'il s'agit d'une boucle sur des index et non sur les valeurs elles-mêmes. Nous utilisons donc une arithmétique for-loop au lieu de la boucle améliorée "for-each". Mais cela suggère que nous pouvons diffuser en utilisant les index pour générer des sous-plages au lieu de transmettre des valeurs et de placer la logique dans le collecteur (comme cela a été fait par la solution proposée par Joop Eggen ).

Une fois que nous avons réalisé cela, nous pouvons voir que chaque position de null dans l'entrée est le délimiteur d'une sous-liste: c'est l'extrémité droite de la sous-liste à gauche, et elle (plus une) est l'extrémité gauche de la sous-liste à la droite. Si nous pouvons gérer les cas Edge, cela conduit à une approche dans laquelle nous trouvons les index auxquels les éléments null se produisent, les mappons en sous-listes et en collectons les sous-listes.

Le code résultant est le suivant:

static List<List<String>> splitStream(List<String> input) {
    int[] indexes = Stream.of(IntStream.of(-1),
                              IntStream.range(0, input.size())
                                       .filter(i -> input.get(i) == null),
                              IntStream.of(input.size()))
                          .flatMapToInt(s -> s)
                          .toArray();

    return IntStream.range(0, indexes.length-1)
                    .mapToObj(i -> input.subList(indexes[i]+1, indexes[i+1]))
                    .collect(toList());
}

Obtenir les index auxquels null se produit est assez facile. La pierre d'achoppement est d'ajouter -1 à gauche et size à l'extrémité droite. J'ai choisi d'utiliser Stream.of pour ajouter, puis flatMapToInt pour les aplatir. (J'ai essayé plusieurs autres approches mais celle-ci semblait être la plus propre.)

Il est un peu plus pratique d'utiliser des tableaux pour les index ici. Premièrement, la notation pour accéder à un tableau est plus agréable que pour une liste: indexes[i] vs indexes.get(i). Deuxièmement, l'utilisation d'un tableau évite la boxe.

À ce stade, chaque valeur d'index du tableau (à l'exception du dernier) correspond à un moins de la position de début d'une sous-liste. L'index à sa droite immédiate est la fin de la sous-liste. Nous diffusons simplement sur le tableau et mappons chaque paire d'index dans une sous-liste et collectons la sortie.

Discussion

L’approche des flux est légèrement plus courte que celle de la version à boucle, mais elle est plus dense. La version for-loop est familière, car nous faisons tout le temps ce travail en Java, mais si vous n'êtes pas déjà au courant de ce que cette boucle est censée faire, ce n'est pas évident. Vous devrez peut-être simuler quelques exécutions de boucle avant de comprendre ce que prev fait et pourquoi la sous-liste ouverte doit être fermée après la fin de la boucle. (Au départ, j'avais oublié de l'avoir, mais je l'ai compris lors des tests.)

Je pense que l’approche des flux est plus facile à conceptualiser: obtenez une liste (ou un tableau) indiquant les limites entre les sous-listes. C'est un flux facile à deux lignes. Comme je l’ai dit plus haut, la difficulté consiste à trouver le moyen de fixer les valeurs Edge aux extrémités. S'il existait une meilleure syntaxe pour cela, par exemple,

    // Java plus Pidgin Scala
    int[] indexes =
        [-1] ++ IntStream.range(0, input.size())
                         .filter(i -> input.get(i) == null) ++ [input.size()];

cela rendrait les choses beaucoup moins encombrées. (Ce dont nous avons vraiment besoin, c’est de la compréhension sous forme de tableaux ou de listes.) Une fois que vous avez les index, c’est simple: vous pouvez les mapper dans des sous-listes et les rassembler dans la liste des résultats.

Et bien sûr, cela n’est pas dangereux en parallèle.

UPDATE 2016-02-06

Voici un moyen plus pratique de créer le tableau d’index de sous-liste. Il est basé sur les mêmes principes, mais il ajuste la plage d'index et ajoute certaines conditions au filtre pour éviter d'avoir à concaténer et à mettre à plat les index.

static List<List<String>> splitStream(List<String> input) {
    int sz = input.size();
    int[] indexes =
        IntStream.rangeClosed(-1, sz)
                 .filter(i -> i == -1 || i == sz || input.get(i) == null)
                 .toArray();

    return IntStream.range(0, indexes.length-1)
                    .mapToObj(i -> input.subList(indexes[i]+1, indexes[i+1]))
                    .collect(toList());
}

UPDATE 2016-11-23

J'ai co-présenté une conférence avec Brian Goetz à Devoxx Antwerp 2016, "Thinking In Parallel" ( vidéo ), qui a présenté ce problème et mes solutions. Le problème présenté est une légère variation qui se scinde en "#" au lieu de null, mais il en va autrement. Au cours de la conversation, j'ai mentionné que j'avais subi de nombreux tests unitaires pour résoudre ce problème. Je les ai annexés ci-dessous, en tant que programme autonome, avec les implémentations de ma boucle et de mes flux. Un exercice intéressant pour les lecteurs consiste à rechercher les solutions proposées dans d'autres réponses aux cas de test que j'ai fournis ici, et à voir lesquelles échouent et pourquoi. (Les autres solutions devront être adaptées pour se scinder en fonction d'un prédicat au lieu de se scinder en null.)

import Java.util.*;
import Java.util.function.*;
import Java.util.stream.*;

import static Java.util.Arrays.asList;

public class ListSplitting {
    static final Map<List<String>, List<List<String>>> TESTCASES = new LinkedHashMap<>();
    static {
        TESTCASES.put(asList(),
                  asList(asList()));
        TESTCASES.put(asList("a", "b", "c"),
                  asList(asList("a", "b", "c")));
        TESTCASES.put(asList("a", "b", "#", "c", "#", "d", "e"),
                  asList(asList("a", "b"), asList("c"), asList("d", "e")));
        TESTCASES.put(asList("#"),
                  asList(asList(), asList()));
        TESTCASES.put(asList("#", "a", "b"),
                  asList(asList(), asList("a", "b")));
        TESTCASES.put(asList("a", "b", "#"),
                  asList(asList("a", "b"), asList()));
        TESTCASES.put(asList("#"),
                  asList(asList(), asList()));
        TESTCASES.put(asList("a", "#", "b"),
                  asList(asList("a"), asList("b")));
        TESTCASES.put(asList("a", "#", "#", "b"),
                  asList(asList("a"), asList(), asList("b")));
        TESTCASES.put(asList("a", "#", "#", "#", "b"),
                  asList(asList("a"), asList(), asList(), asList("b")));
    }

    static final Predicate<String> TESTPRED = "#"::equals;

    static void testAll(BiFunction<List<String>, Predicate<String>, List<List<String>>> f) {
        TESTCASES.forEach((input, expected) -> {
            List<List<String>> actual = f.apply(input, TESTPRED);
            System.out.println(input + " => " + expected);
            if (!expected.equals(actual)) {
                System.out.println("  ERROR: actual was " + actual);
            }
        });
    }

    static <T> List<List<T>> splitStream(List<T> input, Predicate<? super T> pred) {
        int[] edges = IntStream.range(-1, input.size()+1)
                               .filter(i -> i == -1 || i == input.size() ||
                                       pred.test(input.get(i)))
                               .toArray();

        return IntStream.range(0, edges.length-1)
                        .mapToObj(k -> input.subList(edges[k]+1, edges[k+1]))
                        .collect(Collectors.toList());
    }

    static <T> List<List<T>> splitLoop(List<T> input, Predicate<? super T> pred) {
        List<List<T>> result = new ArrayList<>();
        int start = 0;

        for (int cur = 0; cur < input.size(); cur++) {
            if (pred.test(input.get(cur))) {
                result.add(input.subList(start, cur));
                start = cur + 1;
            }
        }
        result.add(input.subList(start, input.size()));

        return result;
    }

    public static void main(String[] args) {
        System.out.println("===== Loop =====");
        testAll(ListSplitting::splitLoop);
        System.out.println("===== Stream =====");
        testAll(ListSplitting::splitStream);
    }
}
62
Stuart Marks

La solution consiste à utiliser Stream.collect. Créer un collecteur à l'aide de son modèle de générateur est déjà indiqué comme solution. L'alternative est que l'autre collect surchargé soit un peu plus primitif.

    List<String> strings = Arrays.asList("a", "b", null, "c", null, "d", "e");
    List<List<String>> groups = strings.stream()
            .collect(() -> {
                List<List<String>> list = new ArrayList<>();
                list.add(new ArrayList<>());
                return list;
            },
            (list, s) -> {
                if (s == null) {
                    list.add(new ArrayList<>());
                } else {
                    list.get(list.size() - 1).add(s);
                }
            },
            (list1, list2) -> {
                // Simple merging of partial sublists would
                // introduce a false level-break at the beginning.
                list1.get(list1.size() - 1).addAll(list2.remove(0));
                list1.addAll(list2);
            });

Comme on le voit, je fais une liste de listes de chaînes, où il y a toujours au moins une dernière liste de chaînes (vide).

  • La première fonction crée une liste de départ de listes de chaînes. Il spécifie le résultat (typé) objet.
  • La deuxième fonction est appelée pour traiter chaque élément. C'est une action sur le résultat partiel et un élément.
  • La troisième n'est pas vraiment utilisée, elle intervient dans la parallélisation du traitement, lorsque des résultats partiels doivent être combinés.

Une solution avec un accumulateur:

Comme le fait remarquer @StuartMarks, le combineur ne remplit pas le contrat relatif au parallélisme.

En raison du commentaire de @ArnaudDenoyelle, une version utilisant reduce.

    List<List<String>> groups = strings.stream()
            .reduce(new ArrayList<List<String>>(),
                    (list, s) -> {
                        if (list.isEmpty()) {
                            list.add(new ArrayList<>());
                        }
                        if (s == null) {
                            list.add(new ArrayList<>());
                        } else {
                            list.get(list.size() - 1).add(s);
                        }
                        return list;
                    },
                    (list1, list2) -> {
                            list1.addAll(list2);
                            return list1;
                    });
  • Le premier paramètre est l'objet accumulé.
  • La deuxième fonction s'accumule.
  • Le troisième est le combineur susmentionné.
23
Joop Eggen

S'il vous plaît ne votez pas. Je n'ai pas assez de place pour expliquer cela dans les commentaires .

C'est une solution avec un Stream et un foreach mais c'est strictement équivalent à la solution d'Alexis ou à une boucle foreach (et moins claire, et je ne pouvais pas me débarrasser du constructeur de copie)

List<List<String>> result = new ArrayList<>();
final List<String> current = new ArrayList<>();
list.stream().forEach(s -> {
      if (s == null) {
        result.add(new ArrayList<>(current));
        current.clear();
      } else {
        current.add(s);
      }
    }
);
result.add(current);

System.out.println(result);

Je comprends que vous souhaitiez trouver une solution plus élégante avec Java 8 mais je pense sincèrement qu’elle n’a pas été conçue pour ce cas. Et comme l'a dit M. Spoon, préférez de manière naïve dans ce cas.

8
Arnaud Denoyelle

Bien que la réponse de Marks Stuart soit concise, intuitive et parallèle en toute sécurité (et le meilleur), je souhaite partager une autre solution intéressante qui ne nécessite pas l’astuce début/fin.

Si nous examinons le problème et pensons au parallélisme, nous pouvons facilement résoudre ce problème en adoptant une stratégie de division et de conquête. Au lieu de considérer le problème comme une liste de séries que nous devons traverser, nous pouvons le considérer comme une composition du même problème de base: fractionner une liste à une valeur null. Nous pouvons intuitivement voir assez facilement que nous pouvons résoudre le problème de manière récursive avec la stratégie récursive suivante:

split(L) :
  - if (no null value found) -> return just the simple list
  - else -> cut L around 'null' naming the resulting sublists L1 and L2
            return split(L1) + split(L2)

Dans ce cas, nous recherchons d’abord une valeur null et, au moment où nous en trouvons une, nous coupons immédiatement la liste et appelons un appel récursif sur les sous-listes. Si nous ne trouvons pas null (le cas de base), nous en avons terminé avec cette branche et nous renvoyons simplement la liste. La concaténation de tous les résultats retournera la liste que nous recherchons. 

Une image vaut mieux que mille mots:

enter image description here

L'algorithme est simple et complet: nous n'avons besoin d'aucune astuce spéciale pour gérer les cas Edge du début/de la fin de la liste. Nous n'avons besoin d'aucune astuce spéciale pour gérer les cas Edge tels que les listes vides ou les listes avec uniquement des valeurs null. Ou des listes se terminant par null ou commençant par null.

Une simple mise en œuvre naïve de cette stratégie se présente comme suit:

public List<List<String>> split(List<String> input) {

    OptionalInt index = IntStream.range(0, input.size())
                                 .filter(i -> input.get(i) == null)
                                 .findAny();

    if (!index.isPresent())
        return asList(input);

    List<String> firstHalf  = input.subList(0, index.getAsInt());
    List<String> secondHalf = input.subList(index.getAsInt()+1, input.size());

    return asList(firstHalf, secondHalf).stream()
                 .map(this::split)
                 .flatMap(List::stream)
                 .collect(toList());

}

Nous cherchons d’abord l’index de toute valeur null dans la liste. Si nous n'en trouvons pas, nous retournons la liste. Si nous en trouvons un, nous séparons la liste en 2 sous-listes, les diffusons par dessus et nous appelons à nouveau la méthode split. Les listes résultantes du sous-problème sont ensuite extraites et combinées pour la valeur de retour.

Remarquez que les 2 flux peuvent facilement être mis en parallèle () et que l'algorithme fonctionnera toujours à cause de la décomposition fonctionnelle du problème.

Bien que le code soit déjà assez concis, il peut toujours être adapté de nombreuses manières. Par exemple, au lieu de vérifier la valeur facultative dans le scénario de base, nous pourrions tirer parti de la méthode orElse sur OptionalInt pour renvoyer l'index final de la liste, ce qui nous permettra de réutiliser le second flux et filtrer les listes vides:

public List<List<String>> split(List<String> input) {

    int index =  IntStream.range(0, input.size())
                          .filter(i -> input.get(i) == null)
                          .findAny().orElse(input.size());

    return asList(input.subList(0, index), input.subList(index+1, input.size())).stream()
                 .map(this::split)
                 .flatMap(List::stream)
                 .filter(list -> !list.isEmpty())
                 .collect(toList());
}

L'exemple n'est donné que pour indiquer la simplicité, l'adaptabilité et l'élégance d'une approche récursive. En effet, cette version introduirait une petite pénalité de performance et échouerait si l'entrée était vide (et en tant que telle pourrait nécessiter un contrôle supplémentaire vide)

Dans ce cas, la récursivité pourrait ne pas être la meilleure solution ( Stuart Marks l'algorithme permettant de trouver des index est seulement O(N) et le mappage/découpage de listes a un coût important ), mais il exprime la solution avec un algorithme simple, intuitif et parallélisable, sans aucun effet secondaire.

Je ne vais pas approfondir la complexité et les avantages/inconvénients ou cas d'utilisation avec des critères d'arrêt et/ou une disponibilité partielle des résultats. J'ai simplement ressenti le besoin de partager cette stratégie de solution, car les autres approches étaient simplement itératives ou utilisaient un algorithme de solution trop complexe qui n'était pas parallélisable.

4
Nick Vanderhoven

Voici une autre approche, qui utilise une fonction de regroupement, qui utilise des index de liste pour le regroupement.

Ici, je regroupe l'élément par le premier index qui suit cet élément, avec la valeur null. Ainsi, dans votre exemple, "a" et "b" seraient mis en correspondance avec 2. De plus, je mappe la valeur null à -1 index, qui devrait être supprimé ultérieurement.

List<String> list = Arrays.asList("a", "b", null, "c", null, "d", "e");

Function<String, Integer> indexGroupingFunc = (str) -> {
             if (str == null) {
                 return -1;
             }
             int index = list.indexOf(str) + 1;
             while (index < list.size() && list.get(index) != null) {
                 index++;
             }
             return index;
         };

Map<Integer, List<String>> grouped = list.stream()
               .collect(Collectors.groupingBy(indexGroupingFunc));

grouped.remove(-1);  // Remove null elements grouped under -1
System.out.println(grouped.values()); // [[a, b], [c], [d, e]]

Vous pouvez également éviter d’obtenir le premier index de l’élément null à chaque fois en mettant en cache l’indice min actuel dans une AtomicInteger. La Function mise à jour ressemblerait à ceci:

AtomicInteger currentMinIndex = new AtomicInteger(-1);

Function<String, Integer> indexGroupingFunc = (str) -> {
        if (str == null) {
            return -1;
        }
        int index = names.indexOf(str) + 1;

        if (currentMinIndex.get() > index) {
            return currentMinIndex.get();
        } else {
            while (index < names.size() && names.get(index) != null) {
              index++;
            }
            currentMinIndex.set(index);
            return index;
        }
    };
4
Rohit Jain

C'est un problème très intéressant. Je suis venu avec une solution d'une ligne. C'est peut-être pas très performant mais ça marche.

List<String> list = Arrays.asList("a", "b", null, "c", null, "d", "e");
Collection<List<String>> cl = IntStream.range(0, list.size())
    .filter(i -> list.get(i) != null).boxed()
    .collect(Collectors.groupingBy(
        i -> IntStream.range(0, i).filter(j -> list.get(j) == null).count(),
        Collectors.mapping(i -> list.get(i), Collectors.toList()))
    ).values();

C'est une idée similaire que @Rohit Jain a proposée. Je regroupe l'espace entre les valeurs NULL . Si vous voulez vraiment un List<List<String>>, vous pouvez ajouter:

List<List<String>> ll = cl.stream().collect(Collectors.toList());
3
Flown

Eh bien, après un peu de travail, vous avez mis au point une solution basée sur un flux unique. Il utilise finalement reduce() pour faire le regroupement, ce qui semblait un choix naturel, mais c’était un peu moche de faire entrer les chaînes dans le List<List<String>> requis par

List<List<String>> result = list.stream()
  .map(Arrays::asList)
  .map(x -> new LinkedList<String>(x))
  .map(Arrays::asList)
  .map(x -> new LinkedList<List<String>>(x))
  .reduce( (a, b) -> {
    if (b.getFirst().get(0) == null) 
      a.add(new LinkedList<String>());
    else
      a.getLast().addAll(b.getFirst());
    return a;}).get();

C'est est cependant 1 ligne!

Lorsqu'il est exécuté avec l'entrée de la question,

System.out.println(result);

Produit:

[[a, b], [c], [d, e]]
2
Bohemian

Voici le code par AbacusUtil

List<String> list = N.asList(null, null, "a", "b", null, "c", null, null, "d", "e");
Stream.of(list).splitIntoList(null, (e, any) -> e == null, null).filter(e -> e.get(0) != null).forEach(N::println);

Déclaration : Je suis le développeur de AbacusUtil.

1
user_3380739

Dans ma StreamEx bibliothèque, il existe une méthode groupRuns qui peut vous aider à résoudre ce problème:

List<String> input = Arrays.asList("a", "b", null, "c", null, "d", "e");
List<List<String>> result = StreamEx.of(input)
        .groupRuns((a, b) -> a != null && b != null)
        .remove(list -> list.get(0) == null).toList();

La méthode groupRuns prend une BiPredicate qui, pour la paire d'éléments adjacents, renvoie la valeur true si elles doivent être groupées. Après cela, nous supprimons les groupes contenant des valeurs NULL et rassemblons le reste dans la liste.

Cette solution est adaptée aux parallèles: vous pouvez également l’utiliser pour le flux parallèle. En outre, cela fonctionne bien avec n’importe quelle source de flux (pas seulement les listes d’accès aléatoires comme dans d’autres solutions) et un peu mieux que les solutions basées sur un collecteur, car vous pouvez utiliser n’importe quelle opération de terminal de votre choix sans perte de mémoire intermédiaire.

0
Tagir Valeev

Je regardais la vidéo sur Penser en parallèle de Stuart. Alors décidé de le résoudre avant de voir sa réponse dans la vidéo. Mettra à jour la solution avec le temps. pour l'instant

Arrays.asList(IntStream.range(0, abc.size()-1).
filter(index -> abc.get(index).equals("#") ).
map(index -> (index)).toArray()).
stream().forEach( index -> {for (int i = 0; i < index.length; i++) {
                    if(sublist.size()==0){
                        sublist.add(new ArrayList<String>(abc.subList(0, index[i])));
                    }else{

                    sublist.add(new ArrayList<String>(abc.subList(index[i]-1, index[i])));
                    }
                }
    sublist.add(new ArrayList<String>(abc.subList(index[index.length-1]+1, abc.size())));
});
0

Avec String on peut faire:

String s = ....;
String[] parts = s.split("sth");

Si toutes les collections séquentielles (comme String est une séquence de caractères) avaient cette abstraction, cela pourrait également être fait pour elles:

List<T> l = ...
List<List<T>> parts = l.split(condition) (possibly with several overloaded variants)

Si nous limitons le problème original à List of Strings (et imposons certaines restrictions au contenu de ses éléments), nous pourrions le pirater comme ceci:

String als = Arrays.toString(new String[]{"a", "b", null, "c", null, "d", "e"});
String[] sa = als.substring(1, als.length() - 1).split("null, ");
List<List<String>> res = Stream.of(sa).map(s -> Arrays.asList(s.split(", "))).collect(Collectors.toList());

(s'il vous plaît ne prenez pas au sérieux si :))

Sinon, la vieille récursion fonctionne aussi:

List<List<String>> part(List<String> input, List<List<String>> acc, List<String> cur, int i) {
    if (i == input.size()) return acc;
    if (input.get(i) != null) {
        cur.add(input.get(i));
    } else if (!cur.isEmpty()) {
        acc.add(cur);
        cur = new ArrayList<>();
    }
    return part(input, acc, cur, i + 1);
}

(note dans ce cas null doit être ajouté à la liste d'entrée)

part(input, new ArrayList<>(), new ArrayList<>(), 0)
0
janek

Grouper par jeton différent chaque fois que vous trouvez un null (ou un séparateur). J'ai utilisé ici un entier différent (utilisé atomique comme titulaire)

Ensuite, remappez la carte générée pour la transformer en une liste de listes.

AtomicInteger i = new AtomicInteger();
List<List<String>> x = Stream.of("A", "B", null, "C", "D", "E", null, "H", "K")
      .collect(Collectors.groupingBy(s -> s == null ? i.incrementAndGet() : i.get()))
      .entrySet().stream().map(e -> e.getValue().stream().filter(v -> v != null).collect(Collectors.toList()))
      .collect(Collectors.toList());

System.out.println(x);
0
Shadi Moadad