Kotlin中的串联迭代器示例

Some times you might need to iterate through multiple lists. This happens, for example, when implementing a tree traversal algorithm. Thanks to tail recursion and Kotlin, a trivial implementation can be as follows:

tailrec fun countAll(nodes: List<Node>, acc: Int): Int {
    return when {
        nodes.isEmpty() -> acc
        else -> countAll(nodes.first().children + nodes.subList(1, nodes.size), acc + 1)
    }
}

您从调用开始countAll在根节点上(例如countAll(listOf(root),1)),该函数将累计所有子项反复地直到所有的遍历和计数。

这种方法的问题在于,它将大部分时间都花在通过将操作数的所有元素复制到新列表中来产生连接结果的过程。

Flamegraph - naive countAll

从火焰图可以看出,大部分时间都花在了数组列表实例和调用全部添加。

我们在此处串联列表的原因只是为了随着添加更多项而不断迭代节点集合。

Let's make it better

为了避免串联列表的开销,我们可以使用的另一种方法是利用可以跨越列表边界的迭代器。

如果我们有一个,我们可以重写我们的countAll功能如下:

tailrec fun countAll(nodes: Iterator<Node>, acc: Int): Int {
    return when {
        !nodes.hasNext() -> acc
        else -> {
            val node = nodes.next()
            countAll(nodes + node.children.iterator(), acc + 1)
        }
    }
}

🏃‍♂️ Time to code!

在我们理想的实施中,加运算符将负责为子级列表存储迭代器,而不会抢先复制新列表中的所有项目。

让我们看一下迭代器的实现:

class ConcatIterator<T>(iterator: Iterator<T>) : Iterator<T> {
    private val store = ArrayDeque<Iterator<T>>()

    init {
        if (iterator.hasNext())
            store.add(iterator)
    }

    override fun hasNext(): Boolean = when {
        store.isEmpty() -> false
        else -> store.first.hasNext()
    }

    override fun next(): T {
        val t = store.first.next()

        if (!store.first.hasNext())
            store.removeFirst()

        return t
    }

    operator fun plus(iterator: Iterator<T>): ConcatIterator<T> {
        if (iterator.hasNext())
            store.add(iterator)
        return this
    }
}

As the iterators get concatenated they get enqueued in the store. In this case, the store uses an 一种rrayDeque which is a lightweight non-concurrent implementation of a Queue that performs the majority of its operations in amortized constant time.

The last touch

最后的增加使迭代器的使用更加舒适,这是通过实现加运算符迭代器。

operator fun <T> Iterator<T>.plus(iterator: Iterator<T>): ConcatIterator<T> =
    when {
        this is ConcatIterator<T> -> this.plus(iterator)
        iterator is ConcatIterator<T> -> iterator.plus(this)
        else -> ConcatIterator(this).plus(iterator)
    }

此扩展功能可帮助我们通过生成一个迭代器,轻松地将两个现有的迭代器连接成一个新的迭代器。ConcatIterator。 这个功能的好处是它可以重用现有的ConcatIterator实例(如果在两个实例中可用)。

Let the numbers speak

现在,我们已经实现了countAll函数,让我们看看它的性能。

I've tested my assumptions using a little Kotlin playground that I've created to experiment with trees. You can find it here.

以下结果来自针对具有65201277节点的树测试这两种实现的结果。

Total count (countAll without ConcatIterator): 65201277 nodes (9227 ms)
Total count (countAll with ConcatIterator): 65201277 nodes (1288 ms)

如您所见,ConcatIterator版本快了10倍。 我们不再承担连接列表的开销,因此大部分计算都花在了执行计数上。

Conclusion

希望您喜欢。 让我知道您如何解决此类问题,以及在Kotlin中是否有更好的方法来解决此问题。

干杯!

from: https://dev.to//alediaferia/a-concatenated-iterator-example-in-kotlin-1l23

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值