24

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));

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

ストリームを 2 回使い切る必要はありません。必要なのは、ストリームの最初の数値と、残りを別のストリームとして (つまり、Scala のStream.headとに相当する) だけだからStream.tailです。Streamこれを達成するために使用できるJava 8 のメソッドはありますか?

ありがとう。

4

10 に答える 10

11

を分割できないという問題がなかったとしても、遅延ではなく再帰的にメソッド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);
}

IntPredicateこれによりフィルターが再帰的に作成されますが、最終的には s のツリーを作成するかs のツリーを作成するかは問題ではありませんIntStream(IntStream.concatそれが機能した場合のアプローチのように)。フィルターの変更可能なインスタンス フィールドが気に入らない場合は、内部クラスで非表示にすることができます (ラムダ式では非表示にできます)。

于 2013-11-15T17:40:19.863 に答える
4

私の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)

John McClean のソリューションは怠惰ではありません。無限のストリームを供給することはできません。だから私は試行錯誤によって最大許容上限(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ソリューション

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

結果: 素数 = の後の StackOverflowError 134417

私の解決策 (StreamEx)

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

結果: 素数 = の後の StackOverflowError 236167

@frhack ソリューション ( rxjava)

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

結果: 素数 = の後の StackOverflowError 367663

@ホルガーソリューション

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

結果: 素数 = の後の StackOverflowError 368089

私の解決策(述語連結を伴うStreamEx)

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

結果: 素数 = の後の StackOverflowError 368287


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

于 2016-01-19T10:33:04.423 に答える
2

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

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));
}

上記の例では、Tuple2とは、 jOOQ統合テスト用に開発したライブラリであるjOOλSeqから借りた型です。追加の依存関係が必要ない場合は、自分で実装することもできます。

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)
    );
}
于 2014-09-22T14:28:46.940 に答える
1

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

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

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キャッシュの中間データ構造を構築することによって機能します。キャッシングと返済が可能なため、head と tail を直接個別に実装できます。

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は、 jOOλStreamを拡張するシーケンシャル拡張機能も提供し、頭と尾の抽出のための (jOOλ からの) ベースとドメイン オブジェクト (HeadAndTail) ソリューションの両方を備えています。Tuple

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

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

Tagir のリクエストによる更新 -> Scala sieve の Java バージョン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)));
}

注 - どちらも空の実装Streamableを持っていないSequenceMため、 のサイズ チェックStreamableheadAndTailOptional.

最後にプレーンを使用したバージョン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);
于 2016-01-11T06:00:46.253 に答える
0

Holger が提案した方法を使用した別のレシピを次に示します。take(int) メソッドや他の多くのメソッドを使用する可能性を追加するためだけに RxJava を使用します。

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()));


    }

}
于 2015-06-06T11:11:55.553 に答える
0

ヘッド アンド テールを取得するには、Lazy Stream の実装が必要です。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));
    }

}
于 2015-06-06T10:23:27.963 に答える
-2

ストリームの先頭を取得したい場合は、次のようにします。

IntStream.range(1, 5).first();

ストリームの末尾を取得したい場合は、次のようにします。

IntStream.range(1, 5).skip(1);

ストリームの先頭と末尾の両方を取得する場合は、次のようにします。

IntStream s = IntStream.range(1, 5);
int head = s.head();
IntStream tail = s.tail();

素数を見つけたい場合は、次のようにします。

LongStream.range(2, n)
   .filter(i -> LongStream.range(2, (long) Math.sqrt(i) + 1).noneMatch(j -> i % j == 0))
   .forEach(N::println);

詳細を知りたい場合は、AbacusUtilを入手してください。

宣言: 私は AbacusUtil の開発者です。

于 2016-12-01T19:43:07.817 に答える