CUBは子供の白熊

Java SE 8 実践プログラミングの練習問題を解く

第2章 ストリーム API の使い方 : 問題 8 : Stream から Stream を生成

問題 8

public static Stream<T> zip(Stream<T> first, Stream<T> second)

を作成せよ
このメソッドは、first と second から交互に要素を取り出し、どちらかのストリームの要素がなくなったら停止する

解答

いや~、この練習問題は難しかった。
一応、確認プログラムは問題なく動いていますが、私の解答が本当の正解かどうか確信持てません。

私が、ブログで Java SE 8 実践プログラミングの練習問題の解答を公表しようと決めたのは、この練習問題のためです。

Spliteratorバージョン

  1. 二つのStreamから二つのSpliteratorを得る
  2. 二つのSpliteratorからひとつのSpliteratorを生成
  3. 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バージョン

  1. 二つのStreamから二つのIteratorを得る
  2. 二つのIteratorからひとつのIteratorを生成
  3. IteratorからSpliteratorを得る
  4. 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とは何か?

StreamCollection
 Collectionの要素数は有限で確定している
 Streamは無限もあるし、有限でも確定しないケースがある
StreamIterator
 Iteratorなら無限もありうる
 でもIteratorは並列に実行できない
StreamSpliterator
 Spliteratorは Splittable Iterator という意味の造語
 Iteratorの性質を持ち、分割すれば並列に実行できる

さらにおまけ

この zip メソッドJava 8 の開発ビルドには入っていたらしいです。
ただしfirstsecondの型は異なっていて、戻り値の型はPair<T, U>のストリームになってます。
なぜリリース版でなくなったのかは分かりませんが、zip メソッドはともかく Pair クラスは欲し~い。