studio programacion para móviles libro edición desarrollo desarrollar curso aprende aplicaciones list iterator kotlin mutable

programacion - En Kotlin, ¿cómo se modifican los contenidos de una lista mientras se itera?



manual de programacion android pdf (1)

Tengo una lista:

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

Y quiero iterarlo mientras modifico algunos de los valores. Sé que puedo hacerlo con el map pero eso hace una copia de la lista.

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

¿Cómo hago esto sin una copia?

Nota: esta pregunta está intencionalmente escrita y contestada por el autor ( preguntas auto respondidas), de modo que las respuestas idiomáticas a los temas más comunes de Kotlin están presentes en SO. También para aclarar algunas respuestas realmente antiguas escritas para los alfas de Kotlin que no son precisas para el Kotlin actual.


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.

Segundo, para modificar una lista "en el lugar" necesita usar un tipo de lista que sea mutable. En su muestra, use listOf que devuelve la interfaz de la List<T> , y eso es de solo lectura. linkedListOf hacer referencia directamente a la clase de una lista mutable (es decir, ArrayList ), o es Kotlin idiomático usar las funciones de ayuda arrayListOf o linkedListOf para crear una MutableList<T> . Una vez que tenga eso, puede iterar la lista usando el listIterator() que tiene un set() métodos 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 se produce la iteración y es eficiente para todos los tipos de listas. Para hacer esto más fácil, cree funciones de extensión útiles que pueda reutilizar (ver más abajo).

Mutando usando una función de extensión simple:

Puede escribir funciones de extensión para Kotlin que realizan una iteración mutable en cualquier lugar para cualquier implementación de MutableList . Estas funciones en línea funcionará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 una función de extensión mapInPlace (que mantiene la denominación típica para este tipo de funciones, como map y 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) } } }

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 está generalizado para toda la Collection<T> , porque la mayoría de los iteradores solo tienen un método remove() , no set() .

Funciones de extensión para matrices

Puedes manejar matrices genéricas con un método similar:

inline fun <T> Array<T>.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, usa 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) } } }

Sobre la Optimización usando solo 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 usar === o !== es Igualdad de referencia . No vale la pena comprobar equals() o hashCode() porque llamarlos tiene un costo desconocido, y realmente la igualdad referencial captura cualquier intento de cambiar el valor.

Pruebas unitarias para funciones de extensión

Aquí hay casos de prueba unitaria que muestran las funciones funcionando, y también una pequeña comparación con el map() funciones estándar 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) } }