2017-07-24 26 views
4

假設我有一個Java 8數據流的數組:Stream<T>[] streams,我想創建一個Stream,其中新流的每個元素都是一個由選取一個元素組成的數組從每個最初的基礎流(假設他們都是連續的)。將Java8流的數組轉換爲元組流

舉例來說,如果我有:

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

我想返回

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

有一些已經實現了這個碼流?我有一個想法,如何做到這一點,這將是pair case的推廣,但我想知道是否有可重用的東西已經存在。

編輯:對不起,我意識到我需要一些澄清:

  • 我不想創建整個矩陣,我想在某時刻(第一個動態返回一行流/ X/0,然後是B/Y/1等),而不必佔用所有行的內存。對於基本流的大小(例如,取最小值,只要沒有更多元素返回的流就停止),我很合理。

  • 我知道這可以通過首先將基礎流轉換爲迭代器,然後創建一個新的迭代器,next()從每個下劃線迭代器中選取一個元素並返回一個新行。這是我上面鏈接的一對例子,我可以在自己的方式實現它,在這裏我試圖瞭解它是否已經在某個庫中完成(我知道JDK沒有這樣的函數)。

回答

1

OK,它seeems沒有這樣的事情身邊,所以我寫了我自己:

  • TupleSpliterator,建立一個元組spliterator從spliterators數組開始;
  • Tuple Stream Builder,它構建一個元組流,從一組數據流開始並利用元組迭代器。
  • 基於Spliteraror/Iterator允許並行性(在某些條件下),如果您想要簡單一些但順序的,TupleIterator也可用。

在單元測試(herehere)可用的​​用法的例子中,類是本utility package的一部分。

編輯:在Federico的評論之後,我添加了Spliterator實現,注意到基於Iterator的版本不能平行。

+0

迭代器的問題是它們會使您的流順序。如果你確定,那麼你已經找到了你的答案。 –

+1

嗨@FedericoPeraltaSchaffner,天哪!你是對的,但簡單的解決方案應該是實現TupleSplitterator(Stream.spliterator()存在)。我會稍後再做,謝謝你的評論。 – zakmck

1

如果你真正的意思的Stream S作爲輸入一個任意的數字 - 這不是TupleX,我能想到的,但如果你真的知道傳入數據流都是相同的尺寸(無無限流) ,則可能是這將滿足您的需要:第一

@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

類似的東西,但這會創建整個矩陣(請參閱上面的privarit我的評論),我不希望這樣,我寧願把你的時間放在一個迭代器中,並用它來構建一個更動態的流。然而,對我而言,重要的是如何去做(儘管它對其他讀者很有用,而且我可以通過將我的解決方案與他人進行比較來學習),但是如果類似的東西已經在某個庫中實現了。 – zakmck

+1

@zakmck這很有趣,你想要它是懶惰的... – Eugene

+1

@zakmck我也懷疑這已經存在......至少我已經看過「StreamEx」 - 可能是最有名的一個,我沒有看到這樣的事情... – Eugene

3

第一件事,這是一個非常糟糕的主意,讓流的數組,因爲他們不能重複使用,它已經變得複雜複雜,可能的解決方案。

不,這在普通的JDK中是不可能的。沒有zip功能,無論我們有Tuples,所以這恐怕是你能想出的最好的事情:

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

首先,我們需要對數據流的數組轉換爲數組或任何數組否則我們可以遍歷不止一次。其次,如果數組中的流具有不同的長度,那麼您沒有指定該做什麼,只要我們可以從每個集合中提取元素,我就假設標準zip行爲可以加入元素。第三,我在這裏創建了一個所有可能的索引流,用於壓縮(IntStream.range(0, minSize))並手動從每個嵌套數組中提取元素。

這很好用。get()on可選在這裏,因爲計算minSize保證會有東西在那裏。

這是一個比較合理的做法假設我們正在處理的列表的列表:

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的流API增加可能將使我們放棄的minSize計算。

如果你想序列的產生仍然lazy,你根本無法收集結果:

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

有趣的,但我不想創建矩陣,我想創建一個流,動態返回一個新的數組({「A」,「X」,「0」}第一次,第二次{「B」,「Y」,「1」}等),而不需要創建矩陣,我只希望創建每個數組項。我知道這可以通過將流轉換爲迭代器來完成,使用它們來定義一個新的迭代器(它會在每一個下一個()處返回這些數組中的一個),並最終將這個迭代器轉換回新的流。我想知道的是,如果某個圖書館已經實施了這個,或者我必須自己寫這個。 – zakmck

+0

@zakmck我不認爲任何lib都可以做到這一點。看看最後一個例子,我添加了序列的惰性創建實現。這是你想到的嗎? –

1

由於番石榴版本21,您可以使用Streams.zip實用方法,這你想要做什麼,除了它只適用於兩個流。現在

,如果你把你的流型數組流的數據流,你可以使用此Streams.zip方法來進行還原:

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]] 

注意減少之前,您需要映射每個Stream<T>Stream<List<T>> ,以便您可以使用List.addAll壓縮這些流。


編輯:上述工程的代碼,但我有一個關於它的性能和內存佔用的嚴重關注,主要是由於創建一個單一元素的多個列表。

也許使用接受的身份,蓄電池和組合效果更好的的Stream.reduce版本:

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]] 

身份必須是n空列表流,與n作爲streams的長度數組,而累加器使用Streams.zip來壓縮具有元素流的列表流。組合器保持與以前相同:它使用Streams.zip壓縮兩個列表流。