2016-02-01 10 views
9

私はKotlinシーケンス、特に以前の値の簡単な計算ではない複雑なものを試しています。Kotlinの無限シーケンスの再帰的定義

私が定義したい1つの例は、すべての素数のシーケンスです。

次の素数を定義する簡単な方法は、シーケンス内の前の素数のいずれかで割り切れない次の整数です。私はKotlinにこれを翻訳する問題を抱えてい

def primeStream(s: Stream[Int]): Stream[Int] = s.head #:: primeStream(s.tail filter(_ % s.head != 0)) 
val primes = primeStream(Stream.from(2)) 

// first 20 primes 
primes.take(20).toList 

:これはに変換することができScalaで

。スケーラでは、遅れて評価されるシーケンスを返す関数を渡すことができるので動作しますが、私はKotlinで同じことをすることはできません。 Kotlinで

私は

fun primes(seq: Sequence<Int>):Sequence<Int> = sequenceOf(seq.first()) + primes(seq.drop(1).filter {it % seq.first() != 0}) 
val primes = primes(sequence(2) {it + 1}) 

primes.take(20).toList() 

を試みたが、機能がすぐに評価され、無限再帰につながるされているため、明らかに動作しません。

答えて

7

ここで重要なポイントは、その最初の項目が残っているとテールが他の何かに元Sequence尾から変換なまけになるようにSequence変換を実装することです。つまり、アイテムが要求されたときにのみ変換が実行されます。

まずは、簡単な連結のように動作しますが、右のオペランドが遅延評価された怠惰なシーケンスの連結を、実装してみましょう:

public infix fun <T> Sequence<T>.lazyPlus(otherGenerator:() -> Sequence<T>) = 
     object : Sequence<T> { 
      private val thisIterator: Iterator<T> by lazy { [email protected]() } 
      private val otherIterator: Iterator<T> by lazy { otherGenerator().iterator() } 

      override fun iterator() = object : Iterator<T> { 
       override fun next(): T = 
         if (thisIterator.hasNext()) 
          thisIterator.next() 
         else 
          otherIterator.next() 

       override fun hasNext(): Boolean = 
         thisIterator.hasNext() || otherIterator.hasNext() 
      } 
     } 

otherIteratorの怠惰は、すべてのトリックを行います。otherIteratorがアクセスされた場合にのみotherGeneratorは、呼び出されますすなわち、第1のシーケンスが終了したときである。

さて、エラトステネスのふるいの再帰的な変種を書いてみましょう:

fun primesFilter(from: Sequence<Int>): Sequence<Int> = from.iterator().let { 
     val current = it.next() 
     sequenceOf(current) lazyPlus { 
      primesFilter(it.asSequence().filter { it % current != 0 }) 
     } 
    } 

lazyPlusは、私たちを遅延シーケンスの末尾にprimesFilterの別の再帰呼び出しを行うことが許されていること。その後

このアプローチは非常に高速ではないが、素数の全配列は

fun primes(): Sequence<Int> { 
    fun primesFilter(from: Sequence<Int>): Sequence<Int> = from.iterator().let { 
     val current = it.next() 
     sequenceOf(current) lazyPlus { 
      primesFilter(it.asSequence().filter { it % current != 0 }) 
     } 
    } 
    return primesFilter((2..Int.MAX_VALUE).asSequence()) 
} 


として表すことができます。 10,000素数の評価は数秒かかるが、1000秒素数は約0.1秒で放出される。

+1

ニース。私がなぜ '' Sequence .plus(Sequence ) '](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/kotlin.-sequence/plus.html)が怠惰ではないのだろうか? 'lazyPlus'を実装しました... – mfulton26

+1

まあ、'シーケンス .plus(...) 'は怠惰ではありません。 :)結果の 'Sequence 'の項目は遅れて評価されますが、右オペランドの 'Sequence 'オブジェクトは熱心に評価されます。これは 'StackOverflowError'につながるのでここでは避ける必要があります。 – hotkey

+0

これは一般的なライブラリ関数として最適です。それは私のために速く働く。シーケンスをイテレータに変換し、primesFilterの実装で再び元に戻す必要性について説明できますか?あなたがそれをしなければちょっとしたもののように思えますが、怠惰な評価はうまくいかないと思います。これを必要としない可能性はありますか? –

3

あなたはSequence<Sequence<Int>>発電機の内部でSequence<Int>連結を配置し、再度Sequence<Int>にそれを平らにすることができます

fun primes(seq: Sequence<Int>): Sequence<Int> = sequence { 
    seq.take(1) + primes(seq.drop(1).filter { it % seq.first() != 0 }) 
}.flatMap { it } 
val primes = primes(sequence(2) { it + 1 }) 

出力:[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]

これは、しかし少し遅いようです。おそらく、各結果をリストにキャッシュして、素数を再帰的に再計算するのではなく、リストから外すことができます。例:

fun primes() = with(arrayListOf(2, 3)) { 
    asSequence() + sequence(last() + 2) { it + 2 } 
      .filter { all { prime -> it % prime != 0 } } 
      .map { it.apply { add(it) } } 
} 
+0

アイデアは本当に好きですが、生成が非常に遅く、結果のシーケンスは1回しか消費できません。スカラーバージョンは私に1000秒のプライムを与えることができますので、それは私が達成すると予想されるものになります。 +1アイデアのため –

+1

@ LionelPortが合意した。私は、複数回消費することができるキャッシングの例を追加しました。 〜50ミリ秒で私のための最初の1000素数を生成しました。 – mfulton26

1

私の現在の回答は再帰関数を使用しないことです。私はまだ素数の無限のシーケンスを取得することができます。最初の素数と2番目の現在のフィルタリングされたシーケンスとの値のペアとしてシーケンスをモデリングすることによって、素数の無限シーケンスを得ることができます。私は最初の要素だけを選択するためにマップを適用します。

val primes = sequence(2 to sequence(3) {it + 2}) { 
    val currSeq = it.second 
    val nextPrime = currSeq.first() 
    nextPrime to currSeq.filter { it % nextPrime != 0} 
}.map {it.first} 
+0

FYI:その解決策は現在動作していません: '[2,3,4,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61 、67]は '[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71]ではない。 – mfulton26

+0

's/it + 1/it + 2 /'はうまく動作します。 – mfulton26

+0

@ mfulton26ありがとう、私は3でシーケンスを開始して2の倍数のフィルターを見逃しています。 –