获取满足谓词的序列元素,然后在 Kotlin 中从那里继续

Taking sequence elements fulfilling a predicate then continuing from there in Kotlin

在 Kotlin 中,序列有一个 takeWhile 函数,只要它们符合给定的谓词,您就可以获取它们。我想做的是根据该谓词获取项目,以某种方式使用它们,然后更改谓词并获取下一个 "batch"。到目前为止,我还没有真正找到一种纯粹使用序列和迭代器提供的方法来做到这一点。

下面的代码片段说明了这个问题。 primeGenerator() 函数 returns 一个 Sequence 质数(长整数)。假设我想制作列表,每个列表都有相同位数的质数。在创建每个列表时,我会出于某种目的使用它。如果列表符合我正在搜索的内容,则迭代可以结束,否则移动到下一个列表。

val primeIt = primeGenerator().iterator()
var digits = 1
var next: Long? = null
val currentList = ArrayList<Long>()
while (digits < 4) {
    next?.also { currentList.add(it) }
    next = primeIt.next()
    if (next.toString().length > digits) {
        println("Primes with $digits: $currentList")
        currentList.clear()
        digits++
    }
}

在这种情况下,一旦位数超过 3,它就会结束。这工作正常,但我想知道是否有某种方法可以通过纯粹在序列或其迭代器上链接的操作来实现相同的效果。基本上对序列进行分块,但基于谓词而不是集合大小。上面素数的例子只是为了说明,我遵循的是一般原则,而不是只适用于这种情况的东西。

我相信有一种方法可以使用标准库来完成您想要的。先限制序号再groupBy位数

val Int.numberOfDigits 
    get() = this.toString().length
sequenceOf(1,22,333).takeWhile{ it.numberOfDigits < 3 }.groupBy{ it.numberOfDigits }.values

如果您想避免对 groupBy 的急切评估,您可以改用 groupingBy,然后 reduce 可能会将累加器留空。

标准库中没有用于大型(或无限)序列的此类函数,但您可以write such function by yourself(尽管它需要一些额外的代码):

class BufferedIterator<T>(private val iterator: Iterator<T>) : Iterator<T> {

    var current: T? = null
        private set

    var reachedEnd: Boolean = false
        private set

    override fun hasNext(): Boolean = iterator.hasNext().also { reachedEnd = !it }

    override fun next(): T = iterator.next().also { current = it }
}

fun <T> Iterator<T>.buffered() = BufferedIterator(this)

fun <T> BufferedIterator<T>.takeWhile(predicate: (T) -> Boolean): List<T> {
    val list = ArrayList<T>()
    if (reachedEnd) return list
    current?.let {
        if (predicate(it)) list += it
    }
    while (hasNext()) {
        val next = next()
        if (predicate(next)) list += next
        else break
    }
    return list
}

fun main() {
    val sequence = sequence {
        var next = 0
        while (true) {
            yield(next++)
        }
    }
    val iter = sequence.iterator().buffered()
    for (i in 0..3) {
        println(iter.takeWhile { it.toString().length <= i })
    }
}

使用这种方法,您甚至可以轻松处理无限序列。

ardenit's answer seems like the best reusable approach. Since taking "chunks" of a sequence requires some state it doesn't seem likely something easily done in a purely functional manner. Delegating the state to a separate class enveloping the sequence makes sense.

Here's a small snippet showing what I ended up using. This assumes the sequence will not be empty and is (technically) infinite or further results aren't requested at some point.

class ChunkedIterator<T>(seq: Sequence<T>) {
    private val it = seq.iterator()
    var next: T = it.next()
    fun next(predicate: (T) -> Boolean): List<T> {
        val result = ArrayList<T>();
        while (predicate.invoke(next)) {
            result.add(next)
            next = it.next();
        }
        return result
    }
}

实现此目的的一种方法是从原始序列中获取迭代器,然后为每个“take”构建一个新序列 -

val itr = seq.iterator()
val batch1 = itr.asSequence().takeWhile { predicate1(it) }.toList()
val batch2 = itr.asSequence().takeWhile { predicate2(it) }.toList()