Aufteilen von Listen in Unterlisten entlang von Elementen

Ich habe diese Liste ( List ):

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

Und ich möchte etwas wie das:

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

Mit anderen Worten möchte ich meine Liste in Unterlisten unter Verwendung des null als Trennzeichen aufteilen, um eine Liste von Listen zu erhalten ( List<List> ). Ich suche eine Java 8 Lösung. Ich habe es mit Collectors.partitioningBy versucht, aber ich bin mir nicht sicher, wonach ich suche. Vielen Dank!

    Die einzige Lösung, die ich im Moment finde, ist die Implementierung eines eigenen benutzerdefinierten Collectors.

    Bevor ich die Lösung lese, möchte ich ein paar Anmerkungen dazu machen. Ich habe diese Frage mehr als Programmierübung genommen, ich bin mir nicht sicher, ob es mit einem parallelen Strom getan werden kann.

    Sie müssen sich also bewusst sein, dass es im Hintergrund bricht, wenn die Pipeline parallel ausgeführt wird .

    Dies ist kein wünschenswertes Verhalten und sollte vermieden werden . Deshalb werfe ich eine Ausnahme in den Combiner-Teil (anstelle von (l1, l2) -> {l1.addAll(l2); return l1;} ), wie es parallel verwendet wird, wenn Sie die beiden Listen kombinieren, so dass Sie haben eine Ausnahme anstelle eines falschen Ergebnisses.

    Auch dies ist aufgrund des Listenkopierens nicht sehr effizient (obwohl es eine native Methode verwendet, um das zugrundeliegende Array zu kopieren).

    Also hier ist die Kollektor-Implementierung:

     private static Collector>, List>> splitBySeparator(Predicate sep) { final List current = new ArrayList<>(); return Collector.of(() -> new ArrayList>(), (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; } ); } 

    und wie man es benutzt:

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

    Ausgabe:

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


    Wie die Antwort von Joop Eggen herauskommt , scheint es, dass es parallel getan werden kann (geben Sie ihm Kredit!). Damit reduziert sich die benutzerdefinierte Collector-Implementierung auf:

     private static Collector>, List>> splitBySeparator(Predicate sep) { return Collector.of(() -> new ArrayList>(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;}); } 

    was den Paragraphen über Parallelismus ein wenig veraltet macht, jedoch lasse ich es wie es eine gute Erinnerung sein kann.


    Beachten Sie, dass die Stream-API nicht immer ein Ersatz ist. Es gibt Aufgaben, die einfacher und besser geeignet sind, die Streams zu verwenden, und es gibt Tasks, die das nicht tun. In Ihrem Fall könnten Sie auch eine Hilfsmethode dafür erstellen:

     private static  List> splitBySeparator(List list, Predicate< ? super T> predicate) { final List> 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; } 

    und nennen Sie es wie List> list = splitBySeparator(originalList, Objects::isNull); .

    Es kann zur Überprüfung von Kantenfällen verbessert werden.

    Obwohl es bereits mehrere Antworten und eine akzeptierte Antwort gibt, fehlen noch ein paar Punkte in diesem Thema. Erstens scheint der Konsens zu sein, dass das Lösen dieses Problems unter Verwendung von Strömen lediglich eine Übung ist und dass der herkömmliche For-Loop-Ansatz vorzuziehen ist. Zweitens haben die bisher gegebenen Antworten einen Ansatz übersehen, bei dem Array- oder Vektor-Techniken zum Einsatz kommen, die meiner Meinung nach die Stream-Lösung erheblich verbessern.

    Erstens, hier ist eine konventionelle Lösung, zum Zwecke der Diskussion und Analyse:

     static List> splitConventional(List input) { List> 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; } 

    Das ist meistens einfach, aber es gibt ein bisschen Feingefühl. Ein Punkt ist, dass eine ausstehende Unterliste von " prev to cur immer offen ist. Wenn wir auf null stoßen, schließen wir es, fügen es zur Ergebnisliste hinzu und prev . Nach der Schleife schließen wir die Unterliste unbedingt.

    Eine andere Beobachtung ist, dass dies eine Schleife über Indizes ist, nicht über die Werte selbst, daher verwenden wir eine arithmetische For-Schleife anstelle der erweiterten For-Each-Schleife. Aber es deutet darauf hin, dass wir mit den Indizes streamen können, um Teilbereiche zu generieren, anstatt über Werte zu streamen und die Logik in den Kollektor zu setzen (wie es Joop Eggens Lösungsvorschlag getan hat).

    Sobald wir das erkannt haben, können wir sehen, dass jede Position von NULL in der Eingabe das Trennzeichen für eine Unterliste ist: Es ist das rechte Ende der Unterliste auf der linken Seite und es (plus eins) ist das linke Ende der Unterliste das Recht. Wenn wir mit den Kantenfällen umgehen können, führt dies zu einem Ansatz, bei dem wir die Indizes finden, bei denen null auftreten, sie auf Unterlisten abbilden und die Unterlisten sammeln.

    Der resultierende Code lautet wie folgt:

     static List> splitStream(List 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()); } 

    Das Abrufen der Indizes, bei denen null auftritt, ist ziemlich einfach. Der Stolperstein fügt -1 links und die size am rechten Ende hinzu. Ich habe mich dafür entschieden, Stream.of zum Anhängen und dann flatMapToInt zu verwenden, um sie zu flatMapToInt . (Ich habe mehrere andere Ansätze ausprobiert, aber dieser schien am saubersten zu sein.)

    Es ist ein wenig bequemer, hier Arrays für die Indizes zu verwenden. Erstens ist die Notation für den Zugriff auf ein Array schöner als für eine Liste: indexes[i] vs. indexes.get(i) . Zweitens vermeidet das Verwenden eines Arrays das Boxen.

    Zu diesem Zeitpunkt ist jeder Indexwert in dem Array (außer dem letzten) um eins kleiner als die Anfangsposition einer Unterliste. Der Index zu seiner unmittelbaren Rechten ist das Ende der Unterliste. Wir streamen einfach über das Array und ordnen jedes Indexpaar einer Unterliste zu und sammeln die Ausgabe.

    Diskussion

    Der Stream-Ansatz ist etwas kürzer als der der For-Loop-Version, aber er ist dichter. Die For-Loop-Version ist bekannt, weil wir diese Dinge in Java ständig machen, aber wenn Sie nicht wissen, was diese Schleife eigentlich machen soll, ist das nicht offensichtlich. Möglicherweise müssen Sie einige Schleifenausführungen simulieren, bevor Sie herausfinden, was prev macht und warum die offene Unterliste nach dem Ende der Schleife geschlossen werden muss. (Ich habe anfangs vergessen, es zu haben, aber ich habe das beim Testen bemerkt.)

    Der Stream-Ansatz ist, glaube ich, einfacher zu verstehen, was passiert: Erhalte eine Liste (oder ein Array), die die Grenzen zwischen den Unterlisten angibt. Das ist ein einfacher Stream-Liner. Die Schwierigkeit besteht, wie oben erwähnt, darin, die Kantenwerte an den Enden anzubringen. Wenn es eine bessere Syntax dafür gäbe, z.

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

    es würde die Dinge viel weniger überladen machen. (Was wir wirklich brauchen, ist Array- oder Listenverständnis.) Sobald Sie die Indizes haben, ist es eine einfache Sache, sie in tatsächliche Unterlisten zu mappen und sie in der Ergebnisliste zu sammeln.

    Und das ist natürlich sicher, wenn Sie parallel laufen.

    UPDATE 2016-02-06

    Hier ist eine bessere Möglichkeit, das Array von Unterlistenindizes zu erstellen. Es basiert auf denselben Prinzipien, passt jedoch den Indexbereich an und fügt dem Filter einige Bedingungen hinzu, um zu vermeiden, dass die Indizes verkettet und abgeglichen werden müssen.

     static List> splitStream(List 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

    Ich hielt einen Vortrag mit Brian Goetz bei Devoxx Antwerpen 2016, "Thinking Parallel" ( Video ), in dem dieses Problem und meine Lösungen vorgestellt wurden. Das dort vorgestellte Problem ist eine kleine Abweichung, die sich auf "#" statt auf Null aufteilt, aber ansonsten ist es dasselbe. In dem Vortrag erwähnte ich, dass ich eine Reihe von Unit-Tests für dieses Problem hatte. Ich habe sie unten als eigenständiges Programm zusammen mit meinen Loop- und Stream-Implementierungen hinzugefügt. Eine interessante Übung für Leser ist es, Lösungen, die in anderen Antworten vorgeschlagen werden, gegen die Testfälle, die ich hier bereitgestellt habe, zu verwenden und zu sehen, welche fehlschlagen und warum. (Die anderen Lösungen müssen angepasst werden, um basierend auf einem Prädikat zu teilen, anstatt auf Null zu teilen.)

     import java.util.*; import java.util.function.*; import java.util.stream.*; import static java.util.Arrays.asList; public class ListSplitting { static final Map, List>> 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 TESTPRED = "#"::equals; static void testAll(BiFunction, Predicate, List>> f) { TESTCASES.forEach((input, expected) -> { List> actual = f.apply(input, TESTPRED); System.out.println(input + " => " + expected); if (!expected.equals(actual)) { System.out.println(" ERROR: actual was " + actual); } }); } static  List> splitStream(List 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  List> splitLoop(List input, Predicate< ? super T> pred) { List> 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); } } 

    Die Lösung besteht darin, Stream.collect zu verwenden. Das Erstellen eines Collectors mit dem Builder-Muster wird bereits als Lösung angegeben. Die Alternative ist die andere überladene collect , die ein kleines bisschen primitiver ist.

      List strings = Arrays.asList("a", "b", null, "c", null, "d", "e"); List> groups = strings.stream() .collect(() -> { List> 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); }); 

    Wie man sieht, mache ich eine Liste von String-Listen, wo immer mindestens eine letzte (leere) String-Liste ist.

    • Die erste function erstellt eine Startliste von String-Listen. Sie gibt das Ergebnis (typisierte) Objekt an.
    • Die zweite function wird aufgerufen, um jedes Element zu verarbeiten. Es ist eine Aktion auf das Teilergebnis und ein Element.
    • Die dritte wird nicht wirklich verwendet, sie kommt bei der Parallelisierung der Verarbeitung ins Spiel, wenn Teilergebnisse kombiniert werden müssen.

    Eine Lösung mit einem Akku:

    Wie @StuartMarks hervorhebt, erfüllt der Combiner nicht den Vertrag für Parallelität.

    Aufgrund des Kommentars von @ArnaudDenoyelle eine Version mit reduce .

      List> groups = strings.stream() .reduce(new ArrayList>(), (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; }); 
    • Der erste Parameter ist das akkumulierte Objekt.
    • Die zweite function akkumuliert.
    • Der dritte ist der oben erwähnte Kombinator.

    Bitte nicht wählen. Ich habe nicht genug Platz, um dies in Kommentaren zu erklären .

    Dies ist eine Lösung mit einem Stream und einer foreach aber das ist streng äquivalent zu Alexis Lösung oder einer foreach Schleife (und weniger klar, und ich konnte den Kopierkonstruktor nicht loswerden):

     List> result = new ArrayList<>(); final List 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); 

    Ich verstehe, dass Sie eine elegantere Lösung mit Java 8 finden möchten, aber ich denke wirklich, dass es nicht für diesen Fall entwickelt wurde. Und wie Herr Löffel sagt, bevorzugen Sie in diesem Fall den naiven Weg.

    Hier ist ein weiterer Ansatz, der eine Gruppierungsfunktion verwendet, die Listen-Indices zum Gruppieren verwendet.

    Hier gruppiere ich das Element nach dem ersten Index nach diesem Element mit dem Wert null . In Ihrem Beispiel würden also "a" und "b" auf 2 abgebildet. Außerdem ordne ich null Wert auf -1 Index, der später entfernt werden sollte.

     List list = Arrays.asList("a", "b", null, "c", null, "d", "e"); Function 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> 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]] 

    Sie können auch vermeiden, immer den ersten Index des null Elements zu erhalten, indem Sie den aktuellen Min-Index in einem AtomicInteger . Die aktualisierte Function wäre wie folgt:

     AtomicInteger currentMinIndex = new AtomicInteger(-1); Function 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; } }; 

    Obwohl die Antwort von Marks Stuart prägnant, intuitiv und parallel sicher (und das Beste) ist , möchte ich eine andere interessante Lösung teilen, die den Start- / Endgrenzentrick nicht benötigt.

    Wenn wir uns die Problemdomäne ansehen und über Parallelität nachdenken, können wir dies leicht mit einer “Divide-and-Conquer” -Strategie lösen. Anstatt das Problem als eine serielle Liste zu betrachten, die wir durchqueren müssen, können wir das Problem als eine Zusammensetzung desselben Grundproblems betrachten: eine Liste mit einem null teilen. Wir können intuitiv leicht erkennen, dass wir das Problem mit der folgenden rekursiven Strategie rekursiv auflösen können:

     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) 

    In diesem Fall suchen wir zunächst nach einem null , und sobald wir einen gefunden haben, schneiden wir die Liste sofort aus und rufen einen rekursiven Aufruf der Unterlisten auf. Wenn wir null (den Basisfall) nicht finden, sind wir mit diesem Zweig fertig und geben die Liste zurück. Die Verkettung aller Ergebnisse gibt die Liste zurück, nach der wir suchen.

    Ein Bild sagt mehr als tausend Worte:

    Bildbeschreibung hier eingeben

    Der Algorithmus ist einfach und vollständig: Wir brauchen keine speziellen Tricks, um die Kantenfälle am Anfang / Ende der Liste zu behandeln. Wir brauchen keine speziellen Tricks, um Randfälle wie leere Listen oder Listen mit nur null zu behandeln. Oder Listen, die mit null enden oder mit null .

    Eine einfache naive Umsetzung dieser Strategie sieht folgendermaßen aus:

     public List> split(List input) { OptionalInt index = IntStream.range(0, input.size()) .filter(i -> input.get(i) == null) .findAny(); if (!index.isPresent()) return asList(input); List firstHalf = input.subList(0, index.getAsInt()); List secondHalf = input.subList(index.getAsInt()+1, input.size()); return asList(firstHalf, secondHalf).stream() .map(this::split) .flatMap(List::stream) .collect(toList()); } 

    Wir suchen zuerst nach dem Index eines beliebigen null in der Liste. Wenn wir keinen finden, geben wir die Liste zurück. Wenn wir einen finden, teilen wir die Liste in 2 Unterlisten auf, streamen über sie und rufen rekursiv die split Methode erneut auf. Die resultierenden Listen des Unterproblems werden dann extrahiert und für den Rückgabewert kombiniert.

    Bemerken Sie, dass die 2 Ströme leicht parallel gemacht werden können () und der Algorithmus wird wegen der funktionalen Zerlegung des Problems noch arbeiten.

    Obwohl der Code bereits sehr übersichtlich ist, kann er immer auf verschiedene Arten angepasst werden. Anstatt beispielsweise den optionalen Wert im Basisfall zu überprüfen, könnten wir die Methode orElse für die OptionalInt nutzen, um den orElse der Liste zurückzugeben, sodass wir den zweiten Stream und zusätzlich noch einmal verwenden können leere Listen herausfiltern:

     public List> split(List 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()); } 

    Das Beispiel gibt nur die Einfachheit, Anpassungsfähigkeit und Eleganz eines rekursiven Ansatzes an. In der Tat würde diese Version eine kleine performanceseinbuße einführen und fehlschlagen, wenn die Eingabe leer wäre (und als solche könnte eine zusätzliche Leerprüfung erforderlich sein) .

    In diesem Fall ist die Rekursion wahrscheinlich nicht die beste Lösung ( Stuart Marks Algorithmus zum Auffinden von Indizes ist nur O (N) und Mapping / Aufspalten von Listen hat erhebliche Kosten), aber sie drückt die Lösung mit einem einfachen, intuitiven, parallelisierbaren Algorithmus aus Nebenwirkungen.

    Ich werde nicht tiefer in die Komplexität und Vorteile / Nachteile oder Anwendungsfälle mit Stoppkriterien und / oder partieller Ergebnisverfügbarkeit einsteigen. Ich hatte gerade das Bedürfnis, diese Lösungsstrategie zu teilen, da die anderen Ansätze lediglich iterativ waren oder einen übermäßig komplexen Lösungsalgorithmus verwendeten, der nicht parallelisierbar war.

    Dies ist ein sehr interessantes Problem. Ich habe eine One-Line-Lösung entwickelt. Es ist vielleicht nicht sehr leistungsfähig, aber es funktioniert.

     List list = Arrays.asList("a", "b", null, "c", null, "d", "e"); Collection> 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(); 

    Es ist eine ähnliche Idee, die @Rohit Jain entwickelt hat. Ich gruppiere den Abstand zwischen den Nullwerten. Wenn Sie wirklich eine List> möchten, können Sie Folgendes hinzufügen:

     List> ll = cl.stream().collect(Collectors.toList()); 

    Nun, nach ein bisschen Arbeit habe ich eine One-Line Stream-basierte Lösung entwickelt. Es verwendet ultimate reduce() , um die Gruppierung zu machen, was die natürliche Wahl zu sein scheint, aber es war ein wenig hässlich, die Strings in die List> die reduce benötigt:

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

    Es ist jedoch 1 Zeile!

    Wenn mit Eingabe von der Frage ausgeführt,

     System.out.println(result); 

    Produziert:

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

    Hier ist Code von AbacusUtil

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

    Erklärung: Ich bin der Entwickler von AbacusUtil.

    In meiner StreamEx- Bibliothek gibt es eine groupRuns Methode, die Ihnen bei der Lösung helfen kann:

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

    Die groupRuns Methode verwendet ein BiPredicate das für das Paar benachbarter Elemente true zurückgibt, wenn sie gruppiert werden sollen. Danach entfernen wir Gruppen mit Nullen und sammeln den Rest zur Liste.

    Diese Lösung ist parallel-freundlich: Sie können es auch für Parallel-Stream verwenden. Außerdem funktioniert es gut mit jeder Stream-Quelle (nicht nur zufällige Access-Listen wie in einigen anderen Lösungen) und es ist etwas besser als Collector-basierte Lösungen, da Sie hier jede Terminal-Operation ohne Zwischenspeichermüll verwenden können.

    Mit String kann man:

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

    Wenn alle sequentiellen Sammlungen (wie der String eine Folge von Zeichen ist) diese Abstraktion haben, könnte dies auch für sie machbar sein:

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

    Wenn wir das ursprüngliche Problem auf die Liste der Strings beschränken (und dem Inhalt der Elemente einige Einschränkungen auferlegen), könnten wir es folgendermaßen hacken:

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

    (Bitte nehmen Sie es nicht ernst :))

    Ansonsten funktioniert auch eine einfache alte Rekursion:

     List> part(List input, List> acc, List 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); } 

    (Beachten Sie, dass in diesem Fall null an die Eingabeliste angehängt werden muss)

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

    Gruppiere nach verschiedenen Token, wenn du eine Null (oder ein Trennzeichen) findest. Ich habe hier eine andere Ganzzahl verwendet (atomar nur als Halter verwendet)

    Ordnen Sie dann die erzeugte Karte neu zu, um sie in eine Liste von Listen umzuwandeln.

     AtomicInteger i = new AtomicInteger(); List> 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); 

    Ich habe mir das Video zu Thinking in Parallel von Stuart angeschaut. Also entschied ich, es zu lösen, bevor ich seine Antwort in dem Video sah. Wird die Lösung mit der Zeit aktualisieren. zur Zeit

     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(abc.subList(0, index[i]))); }else{ sublist.add(new ArrayList(abc.subList(index[i]-1, index[i]))); } } sublist.add(new ArrayList(abc.subList(index[index.length-1]+1, abc.size()))); });