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
に、これを実現するために使用できるメソッドはありますか?
ありがとう。
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のツリーを作成するかIntStream
sのツリーを作成するかは関係ありません(作成した場合のIntStream.concat
アプローチと同様)。作業)。フィルタの可変インスタンスフィールドが気に入らない場合は、内部クラスで非表示にすることができます(ただし、ラムダ式では非表示にできます…)。
以下のソリューションは、ストリームのヘッド/テールの分解を除いて、状態の変更を行いません。
怠惰は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)
私の 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コードに似ているため、より気に入っています。
基本的に次のように実装できます。
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
とSeq
は、 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)
);
}
サードパーティのライブラリを使用してもかまわない場合 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)));
}
注-Streamable
のSequenceM
には空の実装がありません-したがって、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);
頭と尻尾を取得するには、レイジーストリームの実装が必要です。 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));
}
}
ここには多くの興味深い提案がありますが、サードパーティのライブラリに依存しないソリューションが必要な場合は、次のように思いつきました。
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)
);
}
これはホルガーによって提案された方法を使用した別のレシピです。 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()));
}
}