代码之家  ›  专栏  ›  技术社区  ›  Jayson Minard

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

  •  63
  • Jayson Minard  · 技术社区  · 9 年前

    我有一个列表:

    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 ),所以常见问题科特林主题的惯用答案出现在so中。也为了澄清一些为科特林的阿尔法写成的真正古老的答案,这些答案在当今科特林并不准确。

    5 回复  |  直到 4 年前
        1
  •  99
  •   Jayson Minard    9 年前

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

    其次,为了“就地”修改列表,您需要使用一种可变的列表类型。在您的示例中,您使用 listOf 它返回 List<T> 接口,并且是只读的。您需要直接引用可变列表的类(即。 ArrayList ),或者Kotlin习惯使用helper函数 arrayListOf linkedListOf 要创建 MutableList<T> 参考一旦有了这些,就可以使用 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 扩展函数(保持这些类型函数的典型命名,如 map 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() 方法,而不是 设置() .

    阵列的扩展函数

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

    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)
        }
    }
    
        2
  •  2
  •   EntangledLoops    5 年前

    下面是我想到的,这与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
    }
    
        3
  •  0
  •   Maxime Hamm    2 年前

    下面是一个定制的解决方案,示例如下:

    val sorted: MutableList<Pair<Double, T>> = ...
    val groups: mutableListOf<List<T>>() = ...
    
    sorted.forEachMutable { it ->
        if (size + it.first <= maxSize) {
            size += it.first
            group += it.second
            this.remove() // Removing from iterable !
        }
    }
    

    以下是“forEachMutable”的代码:

    fun <T> MutableIterable<T>.forEachMutable(block: Removable<T>.(T) -> Unit): Unit {
        val iterator: MutableIterator<T> = iterator()
        val removable = Removable(iterator, block)
        while (iterator.hasNext()) {
            val item = iterator.next()
            removable.action(item)
        }
    }
    
    class Removable<T>(
        private val iterator: MutableIterator<T>,
        private val block: Removable<T>.(T) -> Unit) {
    
        fun remove() =
            iterator.remove()
    
        fun action(item: T) {
            block(item)
        }
    }
    

    最大值

        4
  •  -2
  •   Byron Katz    4 年前

    不必编写任何新的扩展方法——是的,函数范式很棒,但它们 通常意味着不可变性。如果你正在变异,你可以考虑通过老一套的方式让它变得含蓄:

        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
        }
    
        5
  •  -4
  •   Lalit Hajare    3 年前

    您可以使用列表。对于每个{item->item.modify()}

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