web-dev-qa-db-ja.com

イテレータ<E>から無限のストリーム<E>を作成するにはどうすればよいですか?

私が作成した次のクラスを見てください。

_public class FibonacciSupplier implements Iterator<Integer> {
    private final IntPredicate hasNextPredicate;

    private int beforePrevious = 0;
    private int previous = 1;

    private FibonacciSupplier(final IntPredicate hasNextPredicate) {
        this.hasNextPredicate = hasNextPredicate;
    }

    @Override
    public boolean hasNext() {
        return hasNextPredicate.test(previous);
    }

    @Override
    public Integer next() {
        int result = beforePrevious + previous;
        beforePrevious = previous;
        previous = result;
        return result;
    }

    public static FibonacciSupplier infinite() {
        return new FibonacciSupplier(i -> true);
    }

    public static FibonacciSupplier finite(final IntPredicate predicate) {
        return new FibonacciSupplier(predicate);
    }
} 
_

そしてそれの使用法:

_public class Problem2 extends Problem<Integer> {
    @Override
    public void run() {
        result = toList(FibonacciSupplier.finite(i -> (i <= 4_000_000)))
                .stream()
                .filter(i -> (i % 2 == 0))
                .mapToInt(i -> i)
                .sum();
    }

    @Override
    public String getName() {
        return "Problem 2";
    }

    private static <E> List<E> toList(final Iterator<E> iterator) {
        List<E> list = new ArrayList<>();
        while (iterator.hasNext()) {
            list.add(iterator.next());
        }
        return list;
    }
}
_

無限 _Stream<E>_を作成するにはどうすればよいですか?

Stream<Integer> infiniteStream = toList(FibonacciSupplier.infinite()).stream()を使用した場合、おそらく驚くべきことに、無限のストリームを取得することはありません。
代わりに、基になるメソッドでlistを作成すると、コードは永久にループします。

これまでのところ、これは純粋に理論的なものですが、最初に無限ストリームから最初のx個の数値をスキップし、次に最後のy個の数値で制限したい場合は、その必要性を明確に理解できます。

_int x = MAGIC_NUMBER_X;
int y = MAGIC_NUMBER_y;
int sum = toList(FibonacciSupplier.infinite())
    .stream()
    .skip(x)
    .limit(y)
    .mapToInt(i -> i)
    .sum();
_

コードが結果を返すことはありませんが、どのように行う必要がありますか?

22
skiwi

あなたの間違いは、Iteratorを作成するためにCollectionまたはStreamが必要だと考えることです。無限のストリームを作成するには、値を次々に提供する単一のメソッドで十分です。したがって、クラスFibonacciSupplierの最も簡単な使用法は次のとおりです。

IntStream s=IntStream.generate(FibonacciSupplier.infinite()::next);

または、ボックス化された値を好む場合:

Stream<Integer> s=Stream.generate(FibonacciSupplier.infinite()::next);

この場合、メソッドにnextという名前を付ける必要も、Iteratorインターフェースを満たす必要もないことに注意してください。ただし、クラスと同じかどうかは関係ありません。さらに、nextメソッドをSupplierとして使用するようにストリームに指示したので、hasNextメソッドが呼び出されることはありません。それはただ無限です。

Iteratorを使用して有限ストリームを作成するのは、もう少し複雑です。

Stream<Integer> s=StreamSupport.stream(
  Spliterators.spliteratorUnknownSize(
    FibonacciSupplier.finite(intPredicate), Spliterator.ORDERED),
  false);

この場合、ボックス化されていないIntStream値を持つ有限のintが必要な場合は、FibonacciSupplierPrimitiveIterator.OfIntを実装する必要があります。

23
Holger

Java 8)には、インターフェイスを実装するパブリックの具象クラスはありません Stream 。ただし、いくつかの静的ファクトリメソッド。最も重要なものの1つは StreamSupport.stream です。特に、デフォルトメソッドCollection.stream –ほとんどのコレクションクラスに継承されます:

_default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}
_

このメソッドのデフォルトの実装では、spliterator()を呼び出して Spliterator を作成し、作成したオブジェクトをファクトリメソッドに渡します。 Spliteratorは、並列ストリームをサポートするためにJava 8で導入された新しいインターフェースです)。 toIteratorですが、後者とは対照的に、Spliteratorはパーツに分割でき、個別に処理できます。-を参照してください。 Spliterator.trySplit 詳細については。

defaultメソッドIterable.spliterator もJava 8に追加されたため、すべてのIterableクラスは自動的にSpliteratorsをサポートします。実装は次のようになります。

_default Spliterator<T> spliterator() {
    return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
_

このメソッドは、任意のIteratorからSpliteratorを作成します。これらの2つのステップを組み合わせると、任意のイテレータからStreamを作成できます。

_<T> Stream<T> stream(Iterator<T> iterator) {
    Spliterator<T> spliterator
        = Spliterators.spliteratorUnknownSize(iterator, 0);
    return StreamSupport.stream(spliterator, false);
}
_

Spliteratorsの印象をつかむために、コレクションを使用しない非常に単純な例を次に示します。次のクラスは、Spliteratorを実装して、整数のハーフオープン間隔で反復します。

_public final class IntRange implements Spliterator.OfInt {
    private int first, last;
    public IntRange(int first, int last) {
        this.first = first;
        this.last = last;
    }
    public boolean tryAdvance(IntConsumer action) {
        if (first < last) {
            action.accept(first++);
            return true;
        } else {
            return false;
        }
    }
    public OfInt trySplit() {
        int size = last - first;
        if (size >= 10) {
            int temp = first;
            first += size / 2;
            return new IntRange(temp, first);
        } else {
            return null;
        }
    }
    public long estimateSize() {
        return Math.max(last - first, 0);
    }
    public int characteristics() {
        return ORDERED | DISTINCT | SIZED | NONNULL
            | IMMUTABLE | CONCURRENT | SUBSIZED;
    }
}
_
16
nosid

低レベルのストリームサポートプリミティブとSpliteratorsライブラリを使用して、Iteratorからストリームを作成できます。

StreamSupport.stream()の最後のパラメーターは、ストリームが並列ではないことを示しています。フィボナッチイテレータは以前の反復に依存しているため、必ずそのようにしてください。

return StreamSupport.stream( Spliterators.spliteratorUnknownSize( new Iterator<Node>()
{
    @Override
    public boolean hasNext()
    {
        // to implement
        return ...;
    }

    @Override
    public ContentVersion next()
    {
        // to implement
        return ...;
    }
}, Spliterator.ORDERED ), false );
0
Arnaud Tournier

別の答えを追加するには、特にサンプルコードを考えると、おそらくAbstractSpliteratorの方が適しています。制限を使用する以外に停止条件を与える[良い]方法がないため、Generateは柔軟性がありません。 Limitは述語ではなくいくつかのアイテムのみを受け入れるので、生成したいアイテムの数を知る必要があります-これは不可能かもしれません、そしてジェネレーターが私たちに渡されたブラックボックスである場合はどうなりますか?

AbstractSpliteratorは、スプリッター全体を作成する必要があることと、Iterator/Iterableを使用することの中間にあります。 AbstractSpliteratorには、最初に述語が実行されているかどうかを確認し、実行されていない場合は生成された値をアクションに渡すtryAdvanceメソッドだけがありません。 AbstractIntSpliteratorを使用したフィボナッチ数列の例を次に示します。

public class Fibonacci {
    private static class FibonacciGenerator extends Spliterators.AbstractIntSpliterator
    {
        private IntPredicate hasNextPredicate;
        private int beforePrevious = 0;
        private int previous = 0;

        protected FibonacciGenerator(IntPredicate hasNextPredicate)
        {
            super(Long.MAX_VALUE, 0);
            this.hasNextPredicate = hasNextPredicate;
        }

        @Override
        public boolean tryAdvance(IntConsumer action)
        {
            if (action == null)
            {
                throw new NullPointerException();
            }

            int next = Math.max(1, beforePrevious + previous);
            beforePrevious = previous;
            previous = next;

            if (!hasNextPredicate.test(next))
            {
                return false;
            }

            action.accept(next);

            return true;
        }

        @Override
        public boolean tryAdvance(Consumer<? super Integer> action)
        {
            if (action == null)
            {
                throw new NullPointerException();
            }

            int next = Math.max(1, beforePrevious + previous);
            beforePrevious = previous;
            previous = next;

            if (!hasNextPredicate.test(next))
            {
                return false;
            }

            action.accept(next);

            return true;
        }
    }

    public static void main(String args[])
    {
        Stream<Integer> infiniteStream = StreamSupport.stream(
                new FibonacciGenerator(i -> true), false);

        Stream<Integer> finiteStream = StreamSupport.stream(
                new FibonacciGenerator(i -> i < 100), false);

        // Print with a side-effect for the demo
        infiniteStream.limit(10).forEach(System.out::println);
        finiteStream.forEach(System.out::println);
    }
} 

詳細については、Java 8 in my blog http://thecannycoder.wordpress.com/ でジェネレーターについて説明しました。

0
TheCannyCoder