【问题标题】:In Kotlin, how do you modify the contents of a list while iterating在 Kotlin 中,如何在迭代时修改列表的内容
【发布时间】:2016-04-09 02:13:38
【问题描述】:

我有一个清单:

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 alpha 编写的非常古老的答案,这些答案对于当前的 Kotlin 并不准确。

【问题讨论】:

    标签: list iterator kotlin mutable


    【解决方案1】:

    你可以使用 list.forEach { item -> item.modify() }

    这将在迭代时修改列表中的每个项目。

    【讨论】:

    • 这不适用于列表中任何原始或不可变值本身。
    【解决方案2】:

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

        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
        }
    

    【讨论】:

      【解决方案3】:

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

      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
      }
      

      【讨论】:

        【解决方案4】:

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

        其次,要“就地”修改列表,您需要使用可变的列表类型。在您的示例中,您使用listOf,它返回List&lt;T&gt; 接口,并且是只读的。您需要直接引用可变列表的类(即ArrayList),或者使用辅助函数arrayListOflinkedListOf 创建MutableList&lt;T&gt; 引用是惯用的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)
        }
        

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

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

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

        这是一个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)
                }
            }
        }
        

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

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

        这并不适用于所有Collection&lt;T&gt;,因为大多数迭代器只有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()hashCode(),因为调用它们具有未知的成本,并且实际上引用相等捕获了任何更改值的意图。

        扩展函数的单元测试

        这里是显示功能工作的单元测试用例,以及与复制的 stdlib 函数map() 的小比较:

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

        【讨论】:

        • 你不应该使用MutableListIterator吗?我在迭代器中找不到set
        • 对于原始数组,您应该使用结构相等 != 代替引用相等 !==
        • @AmrElAdawy 在示例中,我使用从可变结构返回的listIterator(),因此返回一个MutableListIterator,因此上述代码有效。如果您正在键入迭代器或使用非可变列表来获取迭代器,那么当然,您将使用 ListIterator 代替。上面的代码推断出正确的类型。
        • @sosite 引用相等是有意的,它询问的是 mutator 是否返回了一个新对象,而不是它是否修改了内容或返回了一个等效对象。这只是一个快速的性能检查,如果这些列表是可观察的,以避免触发事件。对内容进行完全相等性检查会很慢,而且无论如何也不打算这样做。
        • @JaysonMinard 但是原始类型参数的身份相等性已被弃用
        猜你喜欢
        • 2014-09-17
        • 2020-07-28
        • 1970-01-01
        • 1970-01-01
        • 2017-12-05
        • 2019-04-25
        • 2018-10-12
        • 2020-09-07
        • 1970-01-01
        相关资源
        最近更新 更多