CUBは子供の白熊

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

第2章 ストリーム API の使い方 : 問題 11 : ストリームの処理結果を ArrayList に収集

問題 11

単一のArrayListがストリームと同じ大きさで生成されている場合、複数ArrayListをマージしないで、そのArrayListに結果を並行して収集できるはずである。
なぜなら、互いに異なる位置に並行して行うset操作なら、スレッドセーフになるからである。

この収集をどうやって達成することができるか?

解答

この問題は、いまいち出題者の意図が分からない。
そもそも、ストリームのサイズを取得するのは終端操作なので「ストリームと同じ大きさでArrayListを生成」は非現実的である。

それでもトライしてみましょうか…

  1. 二つの要素をまとめるPair<S, T>クラスを導入
  2. メソッドStream<Pair<S, T>> zip(Stream<S> first, Stream<T> second)を実装
    … 練習問題 8 のzipではなく、Java 8 で導入するはずだったもの
  3. zipメソッドStream<Pair<Integer, T>>をゲット
    Pairの第一引数には、収集するArrayListのインデックスをセット
  4. Stream<Pair<Integer, T>>をパラレル化
  5. ストリームの中間操作(filtermap)はPairの第二引数に適用
  6. 終端操作はforEachArrayListに結果をセット

Pair<S, T>クラス

public class Pair<S, T> {
    public final S first;
    public final T second;

    public Pair(S first, T second) {
        this.first = first;
        this.second = second;
    }
}

本来はequalsメソッドhashCodeメソッドを Override するが、ここでは省略

zipメソッドIteratorバージョン)

public static <S,T> Stream<Pair<S,T>> _zip(Stream<S> first, Stream<T> second) {
    Iterator<Pair<S,T>> iterator = new Iterator<Pair<S,T>>() {
        private Iterator<S> _first = first.iterator();
        private Iterator<T> _second = second.iterator();

        public Pair<S,T> next() {
            return new Pair<S,T>(_first.next(), _second.next());
        }
        public boolean hasNext() {
            return _first.hasNext() && _second.hasNext();
        }
    }
    Spliterator<Pair<S,T>> spliterator =
        Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED);
    return StreamSupport.stream(spliterator, false);
}

■ 処理結果を収集

List<String> words = ~;  // ストリームの元ネタ
int size = words.size();
                          // 処理結果を収集する可変長配列
ArrayList<String> list = new ArrayList<String>(Arrays.asList(new String[size]));

zip(IntStream.iterate(0, n -> n + 1).mapToObj(Integer::new), words.stream())
    .parallel()
    .map(UnaryOperator.identity())  // Pair の第二引数を変換
    .forEach(pair -> list.set(pair.first, pair.second));