Kotlin中无限序列的递归定义

时间:2016-02-01 23:42:43

标签: kotlin

我正在试验Kotlin序列,特别是那些不是前一个值的简单计算的更复杂的序列。

我想定义的一个例子是所有素数的序列。

定义下一个素数的一种简单方法是下一个整数,它不能被序列中任何先前的素数整除。

在Scala中,这可以转换为:

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 

我无法将其翻译成Kotlin。在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()

但这显然不起作用,因为函数会立即进行评估并导致无限递归。

3 个答案:

答案 0 :(得分:7)

这里的关键点是实现Sequence转换,使其第一个项目保留,尾部懒惰从原始Sequence尾部转换为其他内容。也就是说,只有在请求项目时才进行转换。

首先,让我们实现延迟序列连接,其行为类似于简单连接,但右边的操作数被懒惰地评估:

public infix fun <T> Sequence<T>.lazyPlus(otherGenerator: () -> Sequence<T>) =
        object : Sequence<T> {
            private val thisIterator: Iterator<T> by lazy { this@lazyPlus.iterator() }
            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的懒惰可以完成所有操作:只有在访问otherGenerator时,即第一个序列完成时,才会调用otherIterator

现在,让我们写一个Eratosthenes筛子的递归变体:

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 :(得分:3)

您可以将Sequence<Int>级联放在Sequence<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]

虽然看起来有点慢。你可能想要的是将每个结果缓存在一个列表中并构建它而不是递归地重新计算质数。 e.g:

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

答案 2 :(得分:1)

我目前的答案是不使用递归函数。我可以通过将序列建模为一对值来获得无限的质数序列,其中第一个是素数,第二个是当前过滤的序列。然后我应用地图只选择第一个元素。

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}