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.
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)
}
}