在Kotlin中,如何在迭代

时间:2016-01-05 09:47:43

标签: list iterator kotlin mutable

我有一个清单:

val someList = listOf(1, 20, 10, 55, 30, 22, 11, 0, 99)

我想在修改某些值时迭代它。我知道我可以用map来做,但是它会复制一份列表。

val copyOfList = someList.map { if (it <= 20) it + 20 else it }

如何在没有副本的情况下执行此操作?

注意: 此问题是由作者(Self-Answered Questions)故意编写和回答的,因此常见问题的Kotlin主题的惯用答案存在于SO中。还要澄清为Kotlin的alphas写的一些非常古老的答案,这些答案对于当前的Kotlin来说是不准确的。

3 个答案:

答案 0 :(得分:54)

首先,并非所有复制列表都不好。有时副本可以利用CPU缓存并且速度非常快,这取决于列表,大小和其他因素。

其次,要“就地”修改列表,您需要使用一种可变的列表。在您的示例中,您使用listOf返回List<T>接口,这是只读的。您需要直接引用可变列表的类(即ArrayList),或者使用辅助函数arrayListOflinkedListOf来创建MutableList<T>引用是惯用的Kotlin 。完成后,您可以使用具有变异方法listIterator()的{​​{1}}来迭代列表。

set()

这将在迭代发生时更改列表中的值,并且对所有列表类型都有效。为了使这更容易,请创建有用的扩展功能,您可以重复使用(见下文)。

使用简单的扩展函数进行变异:

您可以为Kotlin编写扩展函数,为任何// create a mutable list val someList = arrayListOf(1, 20, 10, 55, 30, 22, 11, 0, 99) // iterate it using a mutable iterator and modify values val iterate = someList.listIterator() while (iterate.hasNext()) { val oldValue = iterate.next() if (oldValue <= 20) iterate.set(oldValue + 20) } 实现执行可变迭代迭代。这些内联函数的执行速度与迭代器的任何自定义使用速度一样快,内联函数也是如此。完美适用于Android或任何地方。

以下是MutableList扩展功能(保留了mapInPlacemap这类功能的典型命名:

mapTo

示例调用此扩展函数的任何变体:

inline fun <T> MutableList<T>.mapInPlace(mutator: (T)->T) {
    val iterate = this.listIterator()
    while (iterate.hasNext()) {
        val oldValue = iterate.next()
        val newValue = mutator(oldValue)
        if (newValue !== oldValue) {
            iterate.set(newValue)
        }
    }
}

这并非适用于所有val someList = arrayListOf(1, 20, 10, 55, 30, 22, 11, 0, 99) someList.mapInPlace { if (it <= 20) it + 20 else it } ,因为大多数迭代器只有Collection<T>方法,而不是remove()

数组的扩展函数

您可以使用类似的方法处理通用数组:

set()

对于每个原始数组,使用以下变体:

inline fun <T> Array<T>.mapInPlace(mutator: (T)->T) {
    this.forEachIndexed { idx, value ->
        mutator(value).let { newValue ->
            if (newValue !== value) this[idx] = mutator(value)
        }
    }
}

关于仅使用参考平等的优化

上面的扩展功能通过在未更改为其他实例时未设置值进行优化,检查使用inline fun BooleanArray.mapInPlace(mutator: (Boolean)->Boolean) { this.forEachIndexed { idx, value -> mutator(value).let { newValue -> if (newValue !== value) this[idx] = mutator(value) } } } ===Referential Equality。不值得检查!==equals(),因为调用它们的成本未知,实际上引用相等性会捕获任何更改值的意图。

扩展功能的单元测试

以下是显示函数工作的单元测试用例,以及与复制的stdlib函数hashCode()的小比较:

map()

答案 1 :(得分:2)

这是我想出的,这与杰森类似:

inline fun <T> MutableList<T>.mutate(transform: (T) -> T): MutableList<T> {
    return mutateIndexed { _, t -> transform(t) }
}

inline fun <T> MutableList<T>.mutateIndexed(transform: (Int, T) -> T): MutableList<T> {
    val iterator = listIterator()
    var i = 0
    while (iterator.hasNext()) {
        iterator.set(transform(i++, iterator.next()))
    }
    return this
}

答案 2 :(得分:-1)

无需编写任何新的扩展方法 - 是的,函数范式很棒,但它们确实通常意味着不变性。如果你正在变异,你可能会考虑通过上学来隐含这一点:

    val someList = mutableListOf(1, 20, 10, 55, 30, 22, 11, 0, 99)

    for(i in someList.indices) {
        val value = someList[i]
        someList[i] = if (value <= 20) value + 20 else value
    }