2017-07-24 2 views
4

Angenommen, ich habe ein Array von Java 8-Streams: Stream<T>[] streams, ich möchte einen Stream erstellen, bei dem jedes Element des neuen Streams ein Array ist, das aus einem Element besteht von jedem der anfänglichen Basisströme (angenommen, sie sind alle sequenziell).Ein Array von Java8-Streams in einen Tupelstrom umwandeln

Zum Beispiel, wenn ich:

streams [ 0 ] returning: ("A", "B", "C"), 
    streams [ 1 ] returning ("X", "Y", "Z") 
    and streams [ 2 ] as ("0", "1", "2") 

ich einen Stream möchte die

({ "A", "X", "0" }, { "B", "Y", "1" }, { "C", "Z", "2" }) 

gibt Gibt es einen Code, der dies bereits implementiert? Ich habe eine Idee, wie es geht, es wäre eine Verallgemeinerung der pair case, aber ich würde gerne wissen, ob etwas wiederverwendbar ist schon da ist.

EDIT: sorry, erkannte ich, ich brauche eine Klarstellung:

  • Ich will nicht die ganze Matrix erstellen, möchte ich einen Stream, der eine Zeile zu einem Zeitpunkt dynamisch zurückgibt (erste A/X/0, dann B/Y/1 usw.), ohne den Speicher mit allen Zeilen im voraus belegen zu müssen. Mir geht es gut mit vernünftigen Annahmen über die Größen der Basis-Streams (z. B. das Minimum zu nehmen, zu stoppen, sobald es einen Stream gibt, der keine Elemente mehr zurückgeben muss).

  • Ich weiß, dass dies implementiert werden kann, indem zuerst die Basisströme in Iteratoren umgewandelt werden und dann ein neuer Iterator erstellt wird, der von next() ein Element von jedem der Unterstreichungsiteratoren auswählt und eine neue Zeile zurückgibt. Dies ist, was das Paar, das ich oben verlinkt habe, tut und ich könnte es so auf mich selbst anwenden, hier versuche ich zu verstehen, ob es schon in einer Bibliothek gemacht wurde (ich weiß, JDK hat keine solche Funktion).

Antwort

1

OK, seeems es gibt es nicht so etwas um, so dass ich habe es selbst geschrieben:

  • TupleSpliterator, ein Tupel spliterator aus einem Array von spliterators beginnen zu bauen;
  • Tuple Stream Builder, die einen Tupel-Stream erstellt, ausgehend von einem Array von Streams und einen Tupel-Iterator ausnutzt.
  • Die Spliteraror/Iterator-basierten ermöglichen Parallelität (unter bestimmten Bedingungen), wenn Sie etwas einfacher, aber sequentiell wollen, ist auch eine TupleIterator verfügbar.

Anwendungsbeispiele in Unit-Tests (here und here), sind die Klassen Teil dieser utility package.

EDIT: Ich habe die Spliterator-Implementierung hinzugefügt, nach dem Kommentar von Federico, dass die Iterator-basierte Version nicht parallel sein kann.

+0

Das Problem mit Iteratoren ist, dass sie Ihre Streams sequenziell machen. Wenn Sie damit einverstanden sind, haben Sie Ihre Antwort gefunden. –

+1

Hallo @FedericoPeraltaSchaffner, mein Gott! Sie haben Recht, aber die einfache Lösung sollte auch sein, TupleSplitterator zu realisieren (Stream.spliterator() existiert). Ich werde es später tun, danke für deinen Kommentar. – zakmck

1

Wenn Sie wirklich eine beliebige Anzahl von Stream s als Eingabe bedeuten - das ist nicht TupleX, die ich mir vorstellen kann, aber wenn man wirklich wissen, dass die eingehenden Ströme alle gleich groß sind (keine unendliche Streams) sein, dann kann dies an Ihre Bedürfnisse anzupassen:

@SafeVarargs 
static <T> Stream<Stream<T>> streamOfStreams(Stream<T>... streams) { 

    @SuppressWarnings("unchecked") 
    Iterator<T>[] iterators = new Iterator[streams.length]; 
    for (int i = 0; i < streams.length; ++i) { 
     iterators[i] = streams[i].iterator(); 
    } 

    Iterator<T> first = iterators[0]; 

    Builder<Stream<T>> outer = Stream.builder(); 
    Builder<T> inner = Stream.builder(); 
    while (first.hasNext()) { 
     for (int i = 0; i < streams.length; ++i) { 
      inner.add(iterators[i].next()); 
     } 
     outer.add(inner.build()); 
     inner = Stream.builder(); 
    } 

    return outer.build(); 
} 
+0

so etwas, aber das schafft die ganze Matrix (siehe mein Kommentar zu privarit oben) und ich will das nicht, ich würde lieber Ihre Weile in einen Iterator und verwenden Sie das, um einen dynamischeren Stream zu bauen. Allerdings ist für mich der Punkt nicht viel, wie es zu tun ist (obwohl es für andere Leser nützlich ist und ich kann lernen, indem ich meine Lösungen mit anderen vergleiche), aber wenn so etwas in irgendeiner Bibliothek bereits implementiert wurde. – zakmck

+1

@zakmck das ist interessant, du willst es faul sein ... – Eugene

+1

@zakmck Ich bezweifle auch, dass das schon existiert ... zumindest habe ich in 'StreamEx' geschaut - wahrscheinlich der berühmteste, den es gibt, und ich habe es nicht so etwas gesehen ... – Eugene

3

das Wichtigste zuerst, es ist eine sehr schlechte Idee, eine Reihe von Strömen zu halten, weil sie nicht wiederverwendet werden können und es erschwert schon kompliziert mögliche Lösungen.

Nein, das ist im JDK nicht möglich. Es gibt keine zip Funktionalität, weder haben wir Tuples, so fürchte ich, das ist die beste Sache, die Sie mit oben kommen kann:

Stream[] streams = Stream.of(
    Stream.of("A", "B", "C"), 
    Stream.of("X", "Y", "Z"), 
    Stream.of("0", "1", "2")) 
    .toArray(Stream[]::new); 

String[][] arrays = Arrays.stream(streams) 
    .map(s -> s.toArray(String[]::new)) 
    .toArray(String[][]::new); 

int minSize = Arrays.stream(arrays) 
    .mapToInt(s -> s.length) 
    .min().orElse(0); 

String[][] zipped = IntStream.range(0, minSize) 
    .mapToObj(i -> Arrays.stream(arrays) 
    .map(s -> s[i]) 
    .toArray(String[]::new)) 
    .toArray(String[][]::new); 

Zuerst müssen wir eine Reihe von Strömen in ein Array von Arrays oder irgendetwas konvertieren sonst können wir mehr als einmal durchqueren.

Zweitens, Sie haben nicht angegeben, was zu tun ist, wenn Ströme innerhalb des Arrays unterschiedliche Längen haben, nahm ich Standard zip Verhalten, das Elemente verbindet, solange wir Elemente aus jeder Sammlung extrahieren können.

Drittens erzeuge ich hier einen Strom aller möglichen Indizes für das Zippen (IntStream.range(0, minSize)) und extrahiere Element für Element manuell aus jedem verschachtelten Array.

Es ist in Ordnung zu verwenden.get() on Optional hier, weil die Berechnung von minSize garantiert, dass da etwas drin ist. Hier

ist ein vernünftiger Ansatz davon aus, dass wir mit Listen von Listen handeln:

List<List<String>> lists = Arrays.asList(
    Arrays.asList("A", "B", "C"), 
    Arrays.asList("X", "Y", "Z"), 
    Arrays.asList("0", "1", "2")); 

final int minSize = lists.stream() 
    .mapToInt(List::size) 
    .min().orElse(0); 

List<List<String>> result = IntStream.range(0, minSize) 
    .mapToObj(i -> lists.stream() 
    .map(s -> s.get(i)) 
    .collect(Collectors.toList())) 
    .collect(Collectors.toList()); 

Java 9 Stream API Ergänzungen werden wahrscheinlich erlauben uns die Berechnung von minSize fallen zu lassen.

Wenn Sie die Erzeugung von Sequenzen wollen lazy bleiben, können Sie einfach sammeln nicht die Ergebnisse:

IntStream.range(0, minSize) 
    .mapToObj(i -> lists.stream() 
    .map(s -> s.get(i)) 
    .collect(Collectors.toList())); 
+0

Interessant, aber ich möchte nicht die Matrix erstellen, ich möchte einen Stream erstellen, der dynamisch ein neues Array ({"A", "X", "0"} das erste Mal zurückgibt , {"B", "Y", "1"} das zweite Mal, usw.), ohne die Notwendigkeit, die Matrix zu erstellen, erwarte ich nur, jedes Array-Element zu erstellen. Ich weiß, dass dies getan werden kann, indem man die Streams in Iteratoren umwandelt, sie zum Definieren eines neuen Iterators verwendet (der jedes dieser Arrays bei jedem nächsten() zurückgibt) und schließlich diesen Iterator in einen neuen Stream zurückversetzt. Ich versuche zu verstehen, ob eine Bibliothek das bereits implementiert hat oder ob ich es selbst schreiben muss. – zakmck

+0

@zakmck Ich glaube nicht, dass irgendeine lib das kann. Werfen wir einen Blick auf das letzte Beispiel, ich habe die Implementierung für die faule Erstellung von Sequenzen hinzugefügt. Haben Sie daran gedacht? –

1

Seit Guava Version 21 können Sie die Streams.zip Dienstprogramm-Methode verwenden, das tut, was Sie wollen, es sei denn dass es nur für zwei Streams funktioniert.

Nun, wenn Sie Ihre Array von Strömen in einen Strom der Ströme schalten, können Sie diese Streams.zip Methode verwenden, um eine Reduktion auszuführen:

Stream<List<String>> zipped = Arrays.stream(streams) 
    .map(s -> s.map(e -> { 
     List<String> l = new ArrayList<>(); 
     l.add(e); 
     return l; 
    })) 
    .reduce((s1, s2) -> Streams.zip(s1, s2, (l1, l2) -> { 
     l1.addAll(l2); 
     return l1; 
    })) 
    .orElse(Stream.empty()); 

List<List<String>> tuples = zipped.collect(Collectors.toList()); 

System.out.println(tuples); // [[A, X, 0], [B, Y, 1], [C, Z, 2]] 

Beachten Sie, dass vor dem Reduzieren Sie jede Stream<T>-Stream<List<T>> zur Karte benötigen , so dass Sie List.addAll verwenden können, um die Streams zu komprimieren.


Edit: Der obige Code funktioniert, aber ich habe ernsthafte Bedenken hinsichtlich seiner Leistung und Speicherbedarf, vor allem aufgrund der Erstellung mehrerer Listen von einem einzigen Element.

Vielleicht die Version von Stream.reduce verwenden, die eine Identität annimmt, einen Speicher und ein Kombinierer funktioniert besser:

Stream<List<String>> zipped = Arrays.stream(streams) 
    .reduce(
     IntStream.range(0, streams.length).mapToObj(n -> new ArrayList<>()), 
     (z, s) -> Streams.zip(z, s, (l, e) -> { 
      l.add(e); 
      return l; 
     }), 
     (s1, s2) -> Streams.zip(s1, s2, (l1, l2) -> { 
      l1.addAll(l2); 
      return l1; 
     })); 

List<List<String>> tuples = zipped.collect(Collectors.toList()); 

System.out.println(tuples); // [[A, X, 0], [B, Y, 1], [C, Z, 2]] 

Die Identität ein Strom von n leeren Listen sein muss, mit n die Länge der streams sein Array, während der Akkumulator Streams.zip verwendet, um einen Strom von Listen mit einem Strom von Elementen zu komprimieren. Der Combiner bleibt derselbe wie zuvor: Er verwendet Streams.zip, um zwei Listenströme zu komprimieren.

Verwandte Themen