首页 文章

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

提问于
浏览
22

我有一个清单:

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

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

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

如果没有副本我该怎么办?

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

1 回答

  • 46

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

    其次,要修改列表"in-place",您需要使用一种可变的列表 . 在您的示例中,您使用 listOf 返回 List<T> 接口,这是只读的 . 您需要直接引用可变列表的类(即 ArrayList ),或者是惯用的Kotlin使用辅助函数 arrayListOflinkedListOf 来创建 MutableList<T> 引用 . 完成后,您可以使用具有突变方法 set()listIterator() 迭代列表 .

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

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

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

相关问题