我有一个清单:
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的alphas写的一些非常古老的答案,这些答案对于当前的Kotlin来说是不准确的。
答案 0 :(得分:54)
首先,并非所有复制列表都不好。有时副本可以利用CPU缓存并且速度非常快,这取决于列表,大小和其他因素。
其次,要“就地”修改列表,您需要使用一种可变的列表。在您的示例中,您使用listOf
返回List<T>
接口,这是只读的。您需要直接引用可变列表的类(即ArrayList
),或者使用辅助函数arrayListOf
或linkedListOf
来创建MutableList<T>
引用是惯用的Kotlin 。完成后,您可以使用具有变异方法listIterator()
的{{1}}来迭代列表。
set()
这将在迭代发生时更改列表中的值,并且对所有列表类型都有效。为了使这更容易,请创建有用的扩展功能,您可以重复使用(见下文)。
您可以为Kotlin编写扩展函数,为任何// 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)
}
实现执行可变迭代迭代。这些内联函数的执行速度与迭代器的任何自定义使用速度一样快,内联函数也是如此。完美适用于Android或任何地方。
以下是MutableList
扩展功能(保留了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()
。
您可以使用类似的方法处理通用数组:
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()
,因为调用它们的成本未知,实际上引用相等性会捕获任何更改值的意图。
以下是显示函数工作的单元测试用例,以及与复制的stdlib函数hashCode()
的小比较:
map()
答案 1 :(得分:2)
这是我想出的,这与杰森类似:
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
}
答案 2 :(得分:-1)
无需编写任何新的扩展方法 - 是的,函数范式很棒,但它们确实通常意味着不变性。如果你正在变异,你可能会考虑通过上学来隐含这一点:
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
}