Saltar al contenido

En Kotlin, ¿cómo modifica el contenido de una lista mientras itera?

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. Collectionporque 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.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *