list 在 Kotlin 中,如何在迭代时修改列表的内容

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/34608551/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 08:10:16  来源:igfitidea点击:

In Kotlin, how do you modify the contents of a list while iterating

listiteratorkotlinmutable

提问by Jayson Minard

I have a list:

我有一个清单:

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

And I want to iterate it while modifying some of the values. I know I can do it with mapbut that makes a copy of the list.

我想在修改某些值的同时对其进行迭代。我知道我可以这样做,map但这会复制列表。

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

How do I do this without a copy?

没有副本我怎么做?

Note:this question is intentionally written and answered by the author (Self-Answered Questions), so that the idiomatic answers to commonly asked Kotlin topics are present in SO. Also to clarify some really old answers written for alphas of Kotlin that are not accurate for current-day Kotlin.

注意:这个问题是作者特意写的和回答的(自我回答的问题),所以常见的 Kotlin 主题的惯用答案都在 SO 中。还要澄清一些为 Kotlin alphas 编写的非常旧的答案,这些答案对于当前的 Kotlin 来说是不准确的。

回答by Jayson Minard

First, not all copying of a list is bad. Sometimes a copy can take advantage of CPU cache and be extremely fast, it depends on the list, size, and other factors.

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

Second, for modifying a list "in-place" you need to use a type of list that is mutable. In your sample you use listOfwhich returns the List<T>interface, and that is read-only. You need to directly reference the class of a mutable list (i.e. ArrayList), or it is idiomatic Kotlin to use the helper functions arrayListOfor linkedListOfto create a MutableList<T>reference. Once you have that, you can iterate the list using the listIterator()which has a mutation method set().

其次,要“就地”修改列表,您需要使用一种可变的列表类型。在您的示例中,您使用listOfwhich 返回List<T>接口,这是只读的。您需要直接引用可变列表的类(即ArrayList),或者使用辅助函数arrayListOflinkedListOf创建MutableList<T>引用是 Kotlin 惯用的做法。一旦你有了它,你就可以使用listIterator()具有突变方法的迭代列表set()

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

This will change the values in the list as iteration occurs and is efficient for all list types. To make this easier, create helpful extension functions that you can re-use (see below).

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

Mutating using a simple extension function:

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

You can write extension functions for Kotlin that do an in place mutable iteration for any MutableListimplementation. These inline functions will perform as fast as any custom use of the iterator and is inlined for performance. Perfect for Android or anywhere.

您可以为 Kotlin 编写扩展函数,为任何MutableList实现执行就地可变迭代。这些内联函数的执行速度与迭代器的任何自定义使用一样快,并且内联是为了提高性能。非常适合 Android 或任何地方。

Here is a mapInPlaceextension function (which keeps the naming typical for these type of functions such as mapand mapTo):

这是一个mapInPlace扩展函数(它保留了这些类型函数的典型命名,例如mapmapTo):

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

Examplecalling any variation of this extension function:

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

val someList = arrayListOf(1, 20, 10, 55, 30, 22, 11, 0, 99)
someList.mapInPlace { if (it <= 20) it + 20 else it }

This is not generalized for all Collection<T>, because most iterators only have a remove()method, not set().

这并不适用于所有Collection<T>,因为大多数迭代器只有一个remove()方法,而不是set()

Extension functions for Arrays

数组的扩展函数

You can handle generic arrays with a similar method:

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

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

And for each of the primitive arrays, use a variation of:

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

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

About the Optimization using only Reference Equality

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

The extension functions above optimize a little by not setting the value if it has not changed to a different instance, checking that using ===or !==is Referential Equality. It isn't worth checking equals()or hashCode()because calling those has an unknown cost, and really the referential equality catches any intent to change the value.

上面的扩展函数稍微优化了一点,如果它没有更改为不同的实例,则不设置该值,检查使用 ===!==是否为Referential Equality。不值得检查,equals()或者hashCode()因为调用它们的成本未知,而且实际上引用相等性会捕获任何更改值的意图。

Unit Tests for Extension Functions

扩展功能的单元测试

Here are unit test cases showing the functions working, and also a small comparison to the stdlib function map()that makes a copy:

以下是显示函数工作的单元测试用例,以及与map()生成副本的 stdlib 函数的小比较:

class MapInPlaceTests {
    @Test fun testMutationIterationOfList() {
        val unhappy = setOf("Sad", "Angry")
        val startingList = listOf("Happy", "Sad", "Angry", "Love")
        val expectedResults = listOf("Happy", "Love", "Love", "Love")

        // modify existing list with custom extension function
        val mutableList = startingList.toArrayList()
        mutableList.mapInPlace { if (it in unhappy) "Love" else it }
        assertEquals(expectedResults, mutableList)
    }

    @Test fun testMutationIterationOfArrays() {
        val otherArray = arrayOf(true, false, false, false, true)
        otherArray.mapInPlace { true }
        assertEquals(arrayOf(true, true, true, true, true).toList(), otherArray.toList())
    }

    @Test fun testMutationIterationOfPrimitiveArrays() {
        val primArray = booleanArrayOf(true, false, false, false, true)
        primArray.mapInPlace { true }
        assertEquals(booleanArrayOf(true, true, true, true, true).toList(), primArray.toList())
    }

    @Test fun testMutationIterationOfListWithPrimitives() {
        val otherList = arrayListOf(true, false, false, false, true)
        otherList.mapInPlace { true }
        assertEquals(listOf(true, true, true, true, true), otherList)
    }
}

回答by EntangledLoops

Here's what I came up with, which is a similar approach to Jayson:

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

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
}