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

时间:2019-06-13 10:32:37

标签: 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())
}

是否有一种简单的写法,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>

的一个很好的定义。

7 个答案:

答案 0 :(得分:2)

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
}

答案 1 :(得分:1)

那呢:

    @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)
    }

答案 2 :(得分:1)

要回答问题的最后一部分:

  

我想我必须要拥有不是Sequence才能保持状态的东西,所以也许我真正想要的是对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))
}

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

注意,上面的实现并未考虑传入的非正数的情况,如果该数大于要迭代的余数,则将返回一个列表,该列表的大小小于。我想您可以自己考虑一下这个练习:-)

答案 3 :(得分:0)

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)记住了它的位置,但是不幸的是,由于标准.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())
}

答案 4 :(得分:0)

您可以创建一个函数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

答案 5 :(得分:0)

以下是对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()

答案 6 :(得分:0)

另一种解决方法(类似于上面的the suggestion by Willi Mentzel)是创建一个asStateful()扩展方法,该方法通过将任何序列包装到{{1}中,将任何序列转换为会记住该位置的序列},它总是产生相同的迭代器。

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()

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