查看迭代器

问题描述 投票:0回答:1

Kotlin 中是否有一种方法可以“窥视”迭代器的下一个元素而不推进它?对于示例用例,请考虑此函数用于合并两个预排序的序列:

fun merge(seq1: Sequence<Int>, seq2: Sequence<Int>) = sequence<Int> {
    val it1 = seq1.iterator()
    var current1 = if (it1.hasNext()) it1.next() else null
    val it2 = seq2.iterator()
    var current2 = if (it2.hasNext()) it2.next() else null

    while (current1 != null && current2 != null) {
        if (current1 <= current2) {
            yield(current1)
            current1 = if (it1.hasNext()) it1.next() else null
        } else {
            yield(current2)
            current2 = if (it2.hasNext()) it2.next() else null
        }
    }
    while (current1 != null) {
        yield(current1)
        current1 = if (it1.hasNext()) it1.next() else null
    }
    while (current2 != null) {
        yield(current2)
        current2 = if (it2.hasNext()) it2.next() else null
    }
}

此函数必须跳过重重困难,因为

Iterator
接口无法在不前进的情况下请求下一个元素。换句话说,它不遵守命令-查询分离原则

如果我使用 Guava 的 PeekingIterator:

,我可以使这个函数更加简洁和可读
fun merge(seq1: Sequence<Int>, seq2: Sequence<Int>) = sequence<Int> {
    val it1: PeekingIterator<Int> = Iterators.peekingIterator(seq1.iterator())
    val it2: PeekingIterator<Int> = Iterators.peekingIterator(seq2.iterator())

    while (it1.hasNext() && it2.hasNext())
        yield((if (it1.peek() <= it2.peek()) it1 else it2).next())
    yieldAll(it1)
    yieldAll(it2)
}

同样的功能从24行减少到9行!然而,我在 Kotlin 项目中使用 Guava 感觉不太舒服。 Guava 的创建是为了解决 Java API 的一些限制。 Kotlin 标准库要丰富得多,我们不需要使用第三方 Java 库。有没有办法在不使用 Guava 的情况下使第一个代码示例更加简洁和可读?

附录

这是上述函数的示例单元测试,它应该让您了解它的行为方式:

import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe

class MyTest : FreeSpec({
    "merge test" {
        merge(emptySequence(), emptySequence()).toList() shouldBe emptyList()
        merge(emptySequence(), sequenceOf(1)).toList() shouldBe listOf(1)
        merge(emptySequence(), sequenceOf(1, 3)).toList() shouldBe listOf(1, 3)
        merge(sequenceOf(1), emptySequence()).toList() shouldBe listOf(1)
        merge(sequenceOf(1, 3), emptySequence()).toList() shouldBe listOf(1, 3)
        merge(sequenceOf(0, 3, 4), sequenceOf(1, 4, 7)).toList() shouldBe listOf(0, 1, 3, 4, 4, 7)
    }
kotlin iterator sequence guava
1个回答
1
投票

我认为Kotlin stdlib没有提供这个,但我们可以自己实现它:

fun main() {
    val iter = listOf(1, 2, 3).iterator().peeking()

    println(iter.next()) // 1
    println(iter.peek()) // 2
    println(iter.peek()) // 2
    println(iter.next()) // 2
    println(iter.peek()) // 3
    println(iter.next()) // 3
    println(iter.peek()) // NoSuchElementException
}

fun <T> Iterator<T>.peeking() = object : PeekingIterator<T> {
    private var hasPicked = false
    private var picked: T? = null

    override fun hasNext(): Boolean = hasPicked || [email protected]()

    override fun next(): T  {
        return if (hasPicked) {
            hasPicked = false
            @Suppress("UNCHECKED_CAST")
            picked as T
        } else {
            [email protected]()
        }
    }

    override fun peek(): T {
        if (!hasPicked) {
            hasPicked = true
            picked = [email protected]()
        }
        @Suppress("UNCHECKED_CAST")
        return picked as T
    }
}

interface PeekingIterator<T> : Iterator<T> {
    fun peek(): T
}
© www.soinside.com 2019 - 2024. All rights reserved.