如何从 Kotlin 序列中取出不同的块?

How can I take varying chunks out of a Kotlin Sequence?

如果我有一个 Kotlin 序列,每次调用 take(n) 都会重新启动该序列。

val items = generateSequence(0) {
    if (it > 9) null else it + 1
}

@Test fun `take doesn't remember position`() {
    assertEquals(listOf(0, 1), items.take(2).toList())
    assertEquals(listOf(0, 1, 2), items.take(3).toList())
}

有没有简单的写法 say, another(n) 这样

@Test fun `another does remember position`() {
    assertEquals(listOf(0, 1), items.another(2).toList())
    assertEquals(listOf(2, 3, 4), items.another(3).toList())
}

我想我必须有一些不是 Sequence 的东西来保持状态,所以也许我真正要求的是 fun Iterator<T>.another(count: Int): List<T>[=16 的一个很好的定义=]

这个怎么样:

    @Test
    fun `another does remember position`() {
        val items: Sequence<Int> = generateSequence(0) {
            if (it > 9) null else it + 1
        }

        val (first, rest) = items.another(2)
        assertEquals(listOf(0, 1), first.toList())
        assertEquals(listOf(2, 3, 4), rest.another(3).first.toList())
    }

    fun <T> Sequence<T>.another(n: Int): Pair<Sequence<T>, Sequence<T>> {
        return this.take(n) to this.drop(n)
    }

回答问题的最后一部分:

I suppose that I have to have something that isn't the Sequence to keep the state, so maybe what I'm actually asking for is a nice definition of fun Iterator.another(count: Int): List

一个这样的实现是:

fun <T> Iterator<T>.another(count: Int): List<T> {
    val collectingList = mutableListOf<T>()
    while (hasNext() && collectingList.size < count) {
        collectingList.add(next())
    }
    return collectingList.toList()
}

如果您使用序列生成的迭代器,这将通过您的测试:

@Test
fun `another does remember position`() {
    val items = generateSequence(0) {
        if (it > 9) null else it + 1
    }.iterator() //Use the iterator of this sequence.
    assertEquals(listOf(0, 1), items.another(2))
    assertEquals(listOf(2, 3, 4), items.another(3))
}

对我来说,你所描述的是一个迭代器,因为它允许你遍历集合或序列等,但也记住它的最后位置。

注意上面的实现并没有考虑到传入的 non-positive 计数应该发生什么,如果计数大于要迭代的计数,您将返回一个列表比 n 小。我想您可以将此视为自己的练习:-)

Sequence 不记得它的位置,但它的 iterator 记得:

val iterator : Iterator<Int> = items.iterator()

不幸的是,迭代器没有 take(n),因此要使用 stdlib 中的迭代器,您需要将 iter 包装到 Iterable:

val iterable : Iterable<Int> = items.iterator().asIterable()

fun <T> Iterator<T>.asIterable() : Iterable<T> = object : Iterable<T> {
    private val iter = this@asIterable
    override fun iterator() = iter
}

这让 itareble.take(n) 记住了它的位置,但不幸的是有一个 of-by-one 错误,因为标准 .take(n) 要求一个元素太多:

public fun <T> Iterable<T>.take(n: Int): List<T> {
    require(n >= 0) { "Requested element count $n is less than zero." }
    if (n == 0) return emptyList()
    if (this is Collection<T>) {
        if (n >= size) return toList()
        if (n == 1) return listOf(first())
    }
    var count = 0
    val list = ArrayList<T>(n)
    for (item in this) {
        if (count++ == n)
            break
        list.add(item)
    }
    return list.optimizeReadOnlyList()
}

稍微调整一下就可以解决这个问题:

public fun <T> Iterable<T>.take2(n: Int): List<T> {
    require(n >= 0) { "Requested element count $n is less than zero." }
    if (n == 0) return emptyList()
    if (this is Collection<T>) {
        if (n >= size) return toList()
        if (n == 1) return listOf(first())
    }
    var count = 0
    val list = ArrayList<T>(n)
    for (item in this) {


        list.add(item)
        //count++
        if (++count == n)
            break
    }
    return list
}

现在你们两个测试都通过了:

@Test fun `take does not remember position`() {
    assertEquals(listOf(0, 1), items.take2(2).toList())
    assertEquals(listOf(0, 1, 2), items.take2(3).toList())
}

@Test fun `another does remember position`() {
    assertEquals(listOf(0, 1), iter.take2(2).toList())
    assertEquals(listOf(2, 3, 4), iter.take2(3).toList())
}

Sequence 不记得它的位置,但它的 iterator 记得:

val iterator : Iterator<Int> = items.iterator()

现在你所需要的只是 take(n) 但对于 Iterator<T>:

public fun <T> Iterator<T>.another(n: Int): List<T> {
    require(n >= 0) { "Requested element count $n is less than zero." }
    if (n == 0) return emptyList()
    var count = 0
    val list = ArrayList<T>(n)
    for (item in this) {
        list.add(item)
        if (++count == n)
            break
    }
    return list
}

您可以创建一个函数 generateStatefulSequence,它创建一个序列,该序列通过使用第二个序列的迭代器提供值来保持其状态。

迭代器在该函数的闭包中被捕获。

在每次迭代中,返回序列的种子 lambda ({ i.nextOrNull() }) 从迭代器提供的下一个值开始。

// helper
fun <T> Iterator<T>.nextOrNull() = if(hasNext()) { next() } else null

fun <T : Any> generateStatefulSequence(seed: T?, nextFunction: (T) -> T?): Sequence<T> {
    val i = generateSequence(seed) {
        nextFunction(it)
    }.iterator()

    return generateSequence(
        seedFunction = { i.nextOrNull() }, 
        nextFunction = { i.nextOrNull() }
    )
}

用法:

val s = generateStatefulSequence(0) { if (it > 9) null else it + 1 }
println(s.take(2).toList())  // [0, 1]
println(s.take(3).toList())  // [2, 3, 4]
println(s.take(10).toList()) // [5, 6, 7, 8, 9, 10]

Try it out

根据要求,这是 fun Iterator<T>.another(count: Int): List<T> 的一个很好的定义:

fun <T> Iterator<T>.another(count: Int): List<T> =
        if (count > 0 && hasNext()) listOf(next()) + this.another(count - 1)
        else emptyList()

另一种解决方法(类似于上面的 )是创建一个 asStateful() 扩展方法,通过将任何序列包装成 Iterable 总是产生相同的迭代器。

class StatefulIterable<out T>(wrapped: Sequence<T>): Iterable<T> {
    private val iterator = wrapped.iterator()
    override fun iterator() = iterator
}

fun <T> Sequence<T>.asStateful(): Sequence<T> = StatefulIterable(this).asSequence()

那么你可以这样做:

val items = generateSequence(0) {
    if (it > 9) null else it + 1
}.asStateful()

@Test fun `stateful sequence does remember position`() {
    assertEquals(listOf(0, 1), items.take(2).toList())
    assertEquals(listOf(2, 3, 4), items.take(3).toList())
}

在这里试试:https://pl.kotl.in/Yine8p6wn