第2章 ストリーム API の使い方 : 問題 8 : Stream から Stream を生成
問題 8
public static Stream<T> zip(Stream<T> first, Stream<T> second)
を作成せよ
このメソッドは、first と second から交互に要素を取り出し、どちらかのストリームの要素がなくなったら停止する
解答
いや~、この練習問題は難しかった。
一応、確認プログラムは問題なく動いていますが、私の解答が本当の正解かどうか確信持てません。
私が、ブログで Java SE 8 実践プログラミングの練習問題の解答を公表しようと決めたのは、この練習問題のためです。
■ Spliterator
バージョン
- 二つの
Stream
から二つのSpliterator
を得る - 二つの
Spliterator
からひとつのSpliterator
を生成 Spliterator
からStream
を生成
public static <T> Stream<T> zip(Stream<T> first, Stream<T> second) { Spliterator<T> spliterator = new Spliterator<T>() { private Spliterator<T> even = first.spliterator(); private Spliterator<T> odd = second.spliterator(); private boolean isEven = true; private boolean hasNext = true; public int characteristics() { int ch = even.characteristics() & odd.characteristics(); ch &= (Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.SIZED); return ch | Spliterator.ORDERED; } public long estimateSize() { if (even.hasCharacteristics(SIZED) && odd.hasCharacteristics(SIZED)) { if (even.estimateSize() <= odd.estimateSize()) { return 2 * even.estimateSize(); } else { return 2 * odd.estimateSize() + 1; } } else { return Long.MAX_VALUE; } } public boolean tryAdvance(Consumer<? super T> action) { if (!hasNext) { return false; } hasNext = isEven ? even.tryAdvance(action) : odd.tryAdvance(action); isEven = !isEven; return hasNext; } public Spliterator<T> trySplit() { return null; } }; return StreamSupport.stream(spliterator, false); }
■ Iterator
バージョン
- 二つの
Stream
から二つのIterator
を得る - 二つの
Iterator
からひとつのIterator
を生成 Iterator
からSpliterator
を得るSpliterator
からStream
を生成
public static <T> Stream<T> zip(Stream<T> first, Stream<T> second) { Iterator<T> iterator = new Iterator<T>() { private Iterator<T> even = first.iterator(); private Iterator<T> odd = second.iterator(); private boolean isEven = true; public T next() { T result = isEven ? even.next() : odd.next(); isEven = !isEven; return result; } public boolean hasNext() { return isEven ? even.hasNext() : odd.hasNext(); } }; Spliterator<T> spliterator = Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED); return StreamSupport.stream(spliterator, false); }
このバージョンは、Spliterator
バージョンよりすっきりしていて見通しが良いです。
ただしSpliterator
の特性(characteristics)をきめ細かく設定できません。
■ 動作確認
List<String> words = ~; Stream<String> first = words.stream(); Stream<String> second = Stream.iterate("0", n -> new BigInteger(n).add(BigInteger.ONE).toString()); zip(first, second).forEach(System.out::println);
おまけ
何でこの練習問題が難しかったかと言うと、私は
練習問題は本文の解説の範囲内で解ける
と決めつけていたからです。
Spliterator
の説明が本文に全くなくて、最初は全く関係ない箇所を突いていたんですね。
Stream
を作るためには、Stream
について深く理解する必要があります。
「まず原点に立ち返ろう」です。
■Stream
とは何か?
Stream
≠Collection
Collection
の要素数は有限で確定している
Stream
は無限もあるし、有限でも確定しないケースがある
Stream
≒Iterator
Iterator
なら無限もありうる
でもIterator
は並列に実行できない
Stream
=Spliterator
Spliterator
は Splittable Iterator という意味の造語
Iterator
の性質を持ち、分割すれば並列に実行できる
さらにおまけ
この zip メソッドは Java 8 の開発ビルドには入っていたらしいです。
ただしfirst
とsecond
の型は異なっていて、戻り値の型はPair<T, U>
のストリームになってます。
なぜリリース版でなくなったのかは分かりませんが、zip メソッドはともかく Pair クラスは欲し~い。