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

我有一个列表:

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

我想在修改一些值的时候迭代它。 我知道我可以用map做,但是做了一个副本。

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

我如何在没有副本的情况下做到这一点?

注意: 这个问题是由作者故意写的和回答的( 自我回答的问题 ),所以对于常见的Kotlin话题的习惯性的回答是在SO中。 此外,为了澄清一些真正的古老的答案写为科特林的阿尔法,是不是今天的Kotlin准确。

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

其次,要“就地”修改列表,您需要使用一种可变的列表。 在你的示例中,你使用了listOf ,它返回List<T>接口,而且是只读的。 您需要直接引用可变列表(即ArrayList )的类,或者是惯用的Kotlin使用助手函数arrayListOflinkedListOf来创建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扩展函数(它保持这些类型的函数,如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<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) } } } 

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

上面的扩展函数优化了一点,如果它没有改变为不同的实例,则不设置该值,检查使用===!==是否是引用相等 。 检查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) } }