Dans Kotlin, comment modifier le contenu d'une liste tout en itérant

j'ai une liste:

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

Et je tiens à le parcourir tout en modifiant certaines valeurs. Je sais que je peux le faire avec <!-Mais cela fait une copie de la liste.

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

Comment faire sans copie?

Remarque: cette question est intentionnellement écrite et l'auteur y répond ( Questions Auxquelles On Répond Par Soi-Même), de sorte que les réponses idiomatiques aux sujets de Kotlin couramment demandés sont présents dans SO. Aussi clarifiez quelques réponses vraiment anciennes écrites pour alphas de Kotlin qui ne sont pas exactes pour le Kotlin actuel.

19
demandé sur Jayson Minard 2016-01-05 12:47:42

1 réponses

tout d'abord, toutes les copies d'une liste ne sont pas mauvaises. Parfois, une copie peut profiter du cache CPU et être extrêmement rapide, cela dépend de la liste, de la taille, et d'autres facteurs.

Deuxièmement, pour modifier une liste "en place" vous devez utiliser un type de liste qui est mutable. Dans votre échantillon vous utilisez listOf qui renvoie l' List<T> interface, et qui est en lecture seule. Vous devez faire référence directement à la classe d'une liste mutable (i.e. ArrayList), ou il est idiomatique Kotlin d'utiliser l'aide fonctions arrayListOf ou linkedListOf pour créer un MutableList<T> de référence. Une fois que vous avez cela, vous pouvez itérer la liste en utilisant le listIterator() qui a une méthode de mutation 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)
}

modifier les valeurs dans la liste, comme l'itération se produit et est efficace pour tous les types de liste. Pour faciliter cela, créez des fonctions d'extension utiles que vous pouvez réutiliser (voir ci-dessous).

mutation utilisant une simple fonction d'extension:

vous pouvez écrire l'extension fonctions pour Kotlin qui font une itération mutable en place pour tout MutableList mise en œuvre. Ces fonctions en ligne fonctionneront aussi vite que n'importe quelle Utilisation personnalisée de l'itérateur et sont inlined pour la performance. Parfait pour Android ou n'importe où.

Voici un mapInPlace fonction d'extension (qui conserve l'appellation typique pour ce type de fonctions telles que map et 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)
        }
    }
}

Exemple appel de toute variation de cette extension fonction:

val someList = arrayListOf(1, 20, 10, 55, 30, 22, 11, 0, 99)
someList.mapInPlace { if (it <= 20) it + 20 else it }

ceci n'est pas généralisé pour tous Collection<T>, parce que la plupart des itérateurs seulement un remove() méthode, pas set().

fonctions D'Extension pour les tableaux

vous pouvez manipuler des tableaux génériques avec une méthode similaire:

inline fun <T> Array<T>.mapInPlace(mutator: (T)->T) {
    this.forEachIndexed { idx, value ->
        mutator(value).let { newValue ->
            if (newValue !== value) this[idx] = mutator(value)
        }
    }
}

et pour chacun des tableaux primitifs, utilisez une variation de:

inline fun BooleanArray.mapInPlace(mutator: (Boolean)->Boolean) {
    this.forEachIndexed { idx, value ->
        mutator(value).let { newValue ->
            if (newValue !== value) this[idx] = mutator(value)
        }
    }
}

au sujet de L'optimisation en utilisant seulement L'égalité de référence

les fonctions d'extension ci-dessus optimisent un peu en ne définissant pas la valeur si elle n'a pas changé en une autre instance, en vérifiant qu'en utilisant === ou !== Égalité Référentielle. Ce n'est pas la peine de vérifier equals() ou hashCode() parce que les appeler a un coût inconnu, et vraiment l'égalité référentielle saisit toute intention de changer la valeur.

tests unitaires pour les fonctions D'Extension

Voici des cas de tests unitaires montrant que les fonctions fonctionnent, et aussi une petite comparaison avec la fonction stdlib map() que fait une copie:

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)
    }
}
44
répondu Jayson Minard 2016-01-05 09:52:49