web-dev-qa-db-ja.com

Java 8ストリーム、頭と尾を取得

Java 8では、Scalaの Stream に似た Stream クラスが導入されました。これは、次のようなことを非常に簡潔に行うことができる強力な遅延構造です。

_def from(n: Int): Stream[Int] = n #:: from(n+1)

def sieve(s: Stream[Int]): Stream[Int] = {
  s.head #:: sieve(s.tail filter (_ % s.head != 0))
}

val primes = sieve(from(2))

primes takeWhile(_ < 1000) print  // prints all primes less than 1000
_

Java 8でこれを行うことができるかどうか疑問に思ったので、次のように書きました。

_IntStream from(int n) {
    return IntStream.iterate(n, m -> m + 1);
}

IntStream sieve(IntStream s) {
    int head = s.findFirst().getAsInt();
    return IntStream.concat(IntStream.of(head), sieve(s.skip(1).filter(n -> n % head != 0)));
}

IntStream primes = sieve(from(2));
_

かなり単純ですが、findFirst()skip()の両方がStreamの端末操作であり、1回しか実行できないため、_Java.lang.IllegalStateException: stream has already been operated upon or closed_が生成されます。

必要なのはストリームの最初の番号と残りを別のストリームとして使用することだけなので、実際にはストリームを2回使用する必要はありません。つまり、Scalaの_Stream.head_と_Stream.tail_に相当します。 Java 8 Streamに、これを実現するために使用できるメソッドはありますか?

ありがとう。

23
lyomi

IntStreamを分割できないという問題がなかったとしても、sieveメソッドを遅延ではなく再帰的に呼び出しているため、コードは機能しませんでした。したがって、結果のストリームに最初の値を照会する前に、無限再帰が発生しました。

IntStream sを頭と尾IntStream(まだ消費されていない)に分割することが可能です:

PrimitiveIterator.OfInt it = s.iterator();
int head = it.nextInt();
IntStream tail = IntStream.generate(it::next).filter(i -> i % head != 0);

この場所では、テールでsieveを怠惰に呼び出す構造が必要です。 Streamはそれを提供しません。 concatは既存のストリームインスタンスを引数として想定しており、ラムダ式でレイジー作成がサポートされていない可変状態でのみ機能するため、ラムダ式でsieveをレイジーに呼び出すストリームを構築することはできません。可変状態を非表示にするライブラリ実装がない場合は、可変オブジェクトを使用する必要があります。ただし、可変状態の要件を受け入れると、最初のアプローチよりも解決がさらに簡単になります。

IntStream primes = from(2).filter(i -> p.test(i)).peek(i -> p = p.and(v -> v % i != 0));

IntPredicate p = x -> true;

IntStream from(int n)
{
  return IntStream.iterate(n, m -> m + 1);
}

これにより、フィルターが再帰的に作成されますが、最終的には、IntPredicatesのツリーを作成するかIntStreamsのツリーを作成するかは関係ありません(作成した場合のIntStream.concatアプローチと同様)。作業)。フィルタの可変インスタンスフィールドが気に入らない場合は、内部クラスで非表示にすることができます(ただし、ラムダ式では非表示にできます…)。

10
Holger

以下のソリューションは、ストリームのヘッド/テールの分解を除いて、状態の変更を行いません。

怠惰はIntStream.iterateを使用して取得されます。 Primeクラスは、ジェネレータの状態を維持するために使用されます

    import Java.util.PrimitiveIterator;
    import Java.util.stream.IntStream;
    import Java.util.stream.Stream;

    public class Prime {
        private final IntStream candidates;
        private final int current;

        private Prime(int current, IntStream candidates)
        {
            this.current = current;
            this.candidates = candidates;
        }

        private Prime next()
        {
            PrimitiveIterator.OfInt it = candidates.filter(n -> n % current != 0).iterator();

            int head = it.next();
            IntStream tail = IntStream.generate(it::next);

            return new Prime(head, tail);
        }

        public static Stream<Integer> stream() {
            IntStream possiblePrimes = IntStream.iterate(3, i -> i + 1);

            return Stream.iterate(new Prime(2, possiblePrimes), Prime::next)
                         .map(p -> p.current);
        }
    }

使用法は次のようになります。

Stream<Integer> first10Primes = Prime.stream().limit(10)
3
vidi

私の StreamEx ライブラリには、問題を解決する headTail() 操作があります。

public static StreamEx<Integer> sieve(StreamEx<Integer> input) {
    return input.headTail((head, tail) -> 
        sieve(tail.filter(n -> n % head != 0)).prepend(head));
}

headTailメソッドはBiFunctionを取ります。これは、ストリーム端末操作の実行中に最大1回実行されます。したがって、この実装は怠惰です。トラバーサルが開始されるまで何も計算せず、要求された数の素数のみを計算します。 BiFunctionは最初のストリーム要素headと残りの要素のストリームtailを受け取り、tailを任意の方法で変更できます。事前定義された入力で使用できます。

sieve(IntStreamEx.range(2, 1000).boxed()).forEach(System.out::println);

しかし、無限のストリームも機能します

sieve(StreamEx.iterate(2, x -> x+1)).takeWhile(x -> x < 1000)
     .forEach(System.out::println);
// Not the primes till 1000, but 1000 first primes
sieve(StreamEx.iterate(2, x -> x+1)).limit(1000).forEach(System.out::println);

headTailと述語連結を使用する代替ソリューションもあります。

public static StreamEx<Integer> sieve(StreamEx<Integer> input, IntPredicate isPrime) {
    return input.headTail((head, tail) -> isPrime.test(head) 
            ? sieve(tail, isPrime.and(n -> n % head != 0)).prepend(head)
            : sieve(tail, isPrime));
}

sieve(StreamEx.iterate(2, x -> x+1), i -> true).limit(1000).forEach(System.out::println);

再帰的ソリューションを比較するのは興味深いことです。つまり、生成できる素数の数です。

@ John McCleanソリューション(StreamUtils

JohnMcCleanソリューションは怠惰ではありません。無限のストリームでそれらをフィードすることはできません。だから私は試行錯誤によって最大許容上限(17793)を見つけました(その後StackOverflowErrorが発生します):

public void sieveTest(){
    sieve(IntStream.range(2, 17793).boxed()).forEach(System.out::println);
}

@ John McCleanソリューション(Streamable

public void sieveTest2(){
    sieve(Streamable.range(2, 39990)).forEach(System.out::println);
}

上限を39990より大きくすると、StackOverflowErrorが発生します。

@ frhackソリューション(LazySeq

LazySeq<Integer> ints = integers(2);
LazySeq primes = sieve(ints); // sieve method from @frhack answer
primes.forEach(p -> System.out.println(p));

結果:素数= 53327の後にスタックし、膨大なヒープ割り当てとガベージコレクションが90%以上を占めています。 53323から53327に進むのに数分かかったので、もっと待つのは現実的ではないようです。

@ vidi solution

Prime.stream().forEach(System.out::println);

結果:素数= 134417の後のStackOverflowError。

私のソリューション(StreamEx)

sieve(StreamEx.iterate(2, x -> x+1)).forEach(System.out::println);

結果:素数= 236167の後のStackOverflowError。

@ frhackソリューション(rxjava

Observable<Integer> primes = Observable.from(()->primesStream.iterator());
primes.forEach((x) -> System.out.println(x.toString()));            

結果:素数= 367663の後のStackOverflowError。

@ Holger solution

IntStream primes=from(2).filter(i->p.test(i)).peek(i->p=p.and(v->v%i!=0));
primes.forEach(System.out::println);

結果:素数= 368089の後のStackOverflowError。

私のソリューション(述語連結を使用したStreamEx)

sieve(StreamEx.iterate(2, x -> x+1), i -> true).forEach(System.out::println);

結果:素数= 368287の後のStackOverflowError。


したがって、述語の連結を含む3つのソリューションが勝ちます。これは、新しい条件ごとにスタックフレームが2つしか追加されないためです。私は、それらの間の違いはわずかであり、勝者を定義するために考慮されるべきではないと思います。ただし、最初のStreamExソリューションは、Scalaコードに似ているため、より気に入っています。

3
Tagir Valeev

基本的に次のように実装できます。

static <T> Tuple2<Optional<T>, Seq<T>> splitAtHead(Stream<T> stream) {
    Iterator<T> it = stream.iterator();
    return Tuple(it.hasNext() ? Optional.of(it.next()) : Optional.empty(), seq(it));
}

上記の例では、Tuple2Seqは、 jOOQ 統合テスト用に開発したライブラリである jOOλ から借用したタイプです。追加の依存関係が必要ない場合は、自分で実装することをお勧めします。

class Tuple2<T1, T2> {
    final T1 v1;
    final T2 v2;

    Tuple2(T1 v1, T2 v2) {
        this.v1 = v1;
        this.v2 = v2;
    }

    static <T1, T2> Tuple2<T1, T2> Tuple(T1 v1, T2 v2) {
        return new Tuple<>(v1, v2);
    }
}

static <T> Tuple2<Optional<T>, Stream<T>> splitAtHead(Stream<T> stream) {
    Iterator<T> it = stream.iterator();
    return Tuple(
        it.hasNext() ? Optional.of(it.next()) : Optional.empty,
        StreamSupport.stream(Spliterators.spliteratorUnknownSize(
            it, Spliterator.ORDERED
        ), false)
    );
}
2
Lukas Eder

サードパーティのライブラリを使用してもかまわない場合 cyclops-streams 、私が作成したライブラリには、いくつかの潜在的な解決策があります。

StreamUtils クラスには、headAndTailを含むJava.util.stream.Streamsを直接操作するための多数の静的メソッドがあります。

HeadAndTail<Integer> headAndTail = StreamUtils.headAndTail(Stream.of(1,2,3,4));
int head = headAndTail.head(); //1
Stream<Integer> tail = headAndTail.tail(); //Stream[2,3,4]

Streamable クラスは、再生可能なStreamを表し、遅延キャッシュ中間データ構造を構築することによって機能します。キャッシュと返済が可能なため、ヘッドとテールは直接かつ個別に実装できます。

Streamable<Integer> replayable=  Streamable.fromStream(Stream.of(1,2,3,4));
int head = repayable.head(); //1
Stream<Integer> tail = replayable.tail(); //Stream[2,3,4]

cyclops-streams は、順次Stream拡張も提供します。これは、 jOOλ を拡張し、Tupleベース(jOOλから)とドメインオブジェクトの両方を持ちます(HeadAndTail)頭と尾の抽出のためのソリューション。

SequenceM.of(1,2,3,4)
         .splitAtHead(); //Tuple[1,SequenceM[2,3,4]

SequenceM.of(1,2,3,4)
         .headAndTail();

Tagirのリクエストごとに更新-> A JavaバージョンのScala SequenceMを使用したふるい

public void sieveTest(){
    sieve(SequenceM.range(2, 1_000)).forEach(System.out::println);
}

SequenceM<Integer> sieve(SequenceM<Integer> s){

    return s.headAndTailOptional().map(ht ->SequenceM.of(ht.head())
                            .appendStream(sieve(ht.tail().filter(n -> n % ht.head() != 0))))
                    .orElse(SequenceM.of());
}

そしてStreamable経由の別のバージョン

public void sieveTest2(){
    sieve(Streamable.range(2, 1_000)).forEach(System.out::println);
}

Streamable<Integer> sieve(Streamable<Integer> s){

    return s.size()==0? Streamable.of() : Streamable.of(s.head())
                                                    .appendStreamable(sieve(s.tail()
                                                                    .filter(n -> n % s.head() != 0)));
}

注-StreamableSequenceMには空の実装がありません-したがって、StreamableのサイズチェックとheadAndTailOptionalの使用。

最後に、プレーンJava.util.stream.Streamを使用したバージョン

import static com.aol.cyclops.streams.StreamUtils.headAndTailOptional;

public void sieveTest(){
    sieve(IntStream.range(2, 1_000).boxed()).forEach(System.out::println);
}

Stream<Integer> sieve(Stream<Integer> s){

    return headAndTailOptional(s).map(ht ->Stream.concat(Stream.of(ht.head())
                            ,sieve(ht.tail().filter(n -> n % ht.head() != 0))))
                    .orElse(Stream.of());
}

別の更新-プリミティブではなくオブジェクトを使用する@Holgerのバージョンに基づく怠惰な反復(プリミティブバージョンも可能であることに注意してください)

  final Mutable<Predicate<Integer>> predicate = Mutable.of(x->true);
  SequenceM.iterate(2, n->n+1)
           .filter(i->predicate.get().test(i))
           .peek(i->predicate.mutate(p-> p.and(v -> v%i!=0)))
           .limit(100000)
           .forEach(System.out::println);
1
John McClean

頭と尻尾を取得するには、レイジーストリームの実装が必要です。 Java 8ストリームまたはRxJavaは適していません。

たとえば、次のように LazySeq を使用できます。

レイジーシーケンスは、非常に安価な最初/残りの分解(head()およびtail())を使用して、常に最初からトラバースされます。

LazySeqはJava.util.Listインターフェースを実装しているため、さまざまな場所で使用できます。さらに、コレクション、つまりストリームとコレクターにJava 8つの拡張機能を実装します。


package com.company;

import com.nurkiewicz.lazyseq.LazySeq;

public class Main {

    public static void main(String[] args) {

        LazySeq<Integer> ints = integers(2);
        LazySeq primes = sieve(ints);
        primes.take(10).forEach(p -> System.out.println(p));

    }

    private static LazySeq<Integer> sieve(LazySeq<Integer> s) {
        return LazySeq.cons(s.head(), () -> sieve(s.filter(x -> x % s.head() != 0)));
    }

    private static LazySeq<Integer> integers(int from) {
        return LazySeq.cons(from, () -> integers(from + 1));
    }

}
0
frhack

ここには多くの興味深い提案がありますが、サードパーティのライブラリに依存しないソリューションが必要な場合は、次のように思いつきました。

    import Java.util.AbstractMap;
    import Java.util.Optional;
    import Java.util.Spliterators;
    import Java.util.stream.StreamSupport;

    /**
     * Splits a stream in the head element and a tail stream.
     * Parallel streams are not supported.
     * 
     * @param stream Stream to split.
     * @param <T> Type of the input stream.
     * @return A map entry where {@link Map.Entry#getKey()} contains an
     *    optional with the first element (head) of the original stream
     *    and {@link Map.Entry#getValue()} the tail of the original stream.
     * @throws IllegalArgumentException for parallel streams.
     */
    public static <T> Map.Entry<Optional<T>, Stream<T>> headAndTail(final Stream<T> stream) {
        if (stream.isParallel()) {
            throw new IllegalArgumentException("parallel streams are not supported");
        }
        final Iterator<T> iterator = stream.iterator();
        return new AbstractMap.SimpleImmutableEntry<>(
                iterator.hasNext() ? Optional.of(iterator.next()) : Optional.empty(),
                StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false)
        );
    }
0
Peter

これはホルガーによって提案された方法を使用した別のレシピです。 RxJavaを使用して、take(int)メソッドや他の多くのメソッドを使用する可能性を追加します。

package com.company;

import rx.Observable;

import Java.util.function.IntPredicate;
import Java.util.stream.IntStream;

public class Main {

    public static void main(String[] args) {

        final IntPredicate[] p={(x)->true};
        IntStream primesStream=IntStream.iterate(2,n->n+1).filter(i -> p[0].test(i)).peek(i->p[0]=p[0].and(v->v%i!=0)   );

        Observable primes = Observable.from(()->primesStream.iterator());

        primes.take(10).forEach((x) -> System.out.println(x.toString()));


    }

}
0
frhack