No olvides que en la informática un problema suele tener diversas soluciones, no obstante aquí te enseñaremos lo más óptimo y eficiente.
Solución:
Primero, no todas las copias de una lista son malas. A veces, una copia puede aprovechar la memoria caché de la CPU y ser extremadamente rápida, depende de la lista, el tamaño y otros factores.
En segundo lugar, para modificar una lista “in situ”, debe utilizar un tipo de lista que sea mutable. En tu muestra usas listOf
que devuelve el List
interfaz, y que es de sólo lectura. Debe hacer referencia directamente a la clase de una lista mutable (es decir, ArrayList
), o es Kotlin idiomático usar las funciones auxiliares arrayListOf
o linkedListOf
para crear un MutableList
referencia. Una vez que tenga eso, puede iterar la lista usando el listIterator()
que tiene un método de mutación 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)
Esto cambiará los valores en la lista a medida que ocurra la iteración y es eficiente para todos los tipos de lista. Para hacerlo más fácil, cree útiles funciones de extensión que pueda reutilizar (vea a continuación).
Mutando usando una función de extensión simple:
Puede escribir funciones de extensión para Kotlin que realicen una iteración mutable in situ para cualquier MutableList
implementación. Estas funciones en línea se ejecutarán tan rápido como cualquier uso personalizado del iterador y están en línea para el rendimiento. Perfecto para Android o en cualquier lugar.
Aquí hay un mapInPlace
función de extensión (que mantiene la denominación típica de este tipo de funciones como map
y mapTo
):
inline fun MutableList.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)
Ejemplo llamando a cualquier variación de esta función de extensión:
val someList = arrayListOf(1, 20, 10, 55, 30, 22, 11, 0, 99)
someList.mapInPlace if (it <= 20) it + 20 else it
Esto no es generalizado para todos. Collection
porque la mayoría de los iteradores solo tienen un remove()
método, no set()
.
Funciones de extensión para arreglos
Puede manejar matrices genéricas con un método similar:
inline fun Array.mapInPlace(mutator: (T)->T)
this.forEachIndexed idx, value ->
mutator(value).let newValue ->
if (newValue !== value) this[idx] = mutator(value)
Y para cada una de las matrices primitivas, use una variación de:
inline fun BooleanArray.mapInPlace(mutator: (Boolean)->Boolean)
this.forEachIndexed idx, value ->
mutator(value).let newValue ->
if (newValue !== value) this[idx] = mutator(value)
Acerca de la Optimización usando solo la Igualdad de Referencia
Las funciones de extensión anteriores optimizan un poco al no establecer el valor si no ha cambiado a una instancia diferente, verificando que usando ===
o !==
es la Igualdad Referencial. no vale la pena revisar equals()
o hashCode()
porque llamar a esos tiene un costo desconocido, y realmente la igualdad referencial atrapa cualquier intento de cambiar el valor.
Pruebas unitarias para funciones de extensión
Aquí hay casos de prueba de unidad que muestran el funcionamiento de las funciones, y también una pequeña comparación con la función stdlib map()
que hace una copia:
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)
Esto es lo que se me ocurrió, que es un enfoque similar al de Jayson:
inline fun MutableList.mutate(transform: (T) -> T): MutableList
return mutateIndexed _, t -> transform(t)
inline fun MutableList.mutateIndexed(transform: (Int, T) -> T): MutableList
val iterator = listIterator()
var i = 0
while (iterator.hasNext())
iterator.set(transform(i++, iterator.next()))
return this
Si posees algún asunto y forma de aclarar nuestro sección te inspiramos añadir una anotación y con gusto lo estudiaremos.