В 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 }
как это сделать без копии?
Примечание: этот вопрос намеренно написан и на него отвечает автор (Вопросы С Ответами), так что идиоматические ответы на часто задаваемые темы Котлина присутствуют в SO. Также уточните некоторые действительно старые ответы, написанные для Альф Котлина, которые не точны для современного Котлина.
1 ответов
во-первых, не все копирование списка плохо. Иногда копия может воспользоваться кэшем CPU и быть чрезвычайно быстрой, это зависит от списка, размера и других факторов.
во-вторых, для изменения списка "на месте" вам нужно использовать тип списка, который является изменяемым. В вашем примере вы используете listOf
возвращает List<T>
интерфейс, и это только для чтения. Вам нужно напрямую ссылаться на класс изменяемого списка (т. е. ArrayList
), или это идиоматический Котлин, чтобы использовать помощника функции 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)
}
это изменит значения в списке по мере итерации и эффективно для всех типов списков. Чтобы сделать это проще, создайте полезные функции расширения, которые можно использовать повторно (см. ниже).
мутирование с помощью простой функции расширения:
вы можете написать расширение функции для Котлина, которые делают на месте изменяемую итерацию для любого 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()
способ, а не 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)
}
}
}
об оптимизации с использованием только ссылочного равенства
функции расширения выше оптимизируют немного, не устанавливая значение, если оно не изменилось на другой экземпляр, проверяя, что с помощью ===
или !==
is Равенство Ссылок. Это не стоит проверять 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)
}
}