リストがあります:
val someList = listOf(1, 20, 10, 55, 30, 22, 11, 0, 99)
そして、いくつかの値を変更しながらそれを繰り返したいと思います。 map
でできることはわかっていますが、リストのコピーが作成されます。
val copyOfList = someList.map { if (it <= 20) it + 20 else it }
コピーなしでこれを行うにはどうすればよいですか?
注:この質問は作者によって意図的に書かれ、回答されています(- Self-Answered Questions ) 、そのため、よく聞かれるKotlinのトピックに対する慣用的な回答がSOにあります。また、現在のKotlinでは正確ではないKotlinのアルファ用に書かれたいくつかの本当に古い回答を明確にするため。
まず、リストのすべてのコピーが悪いわけではありません。コピーはCPUキャッシュを利用して非常に高速になる場合があり、リスト、サイズ、およびその他の要因に依存します。
第二に、「インプレース」リストを変更するには、変更可能なタイプのリストを使用する必要があります。サンプルでは、listOf
を使用します。これは_List<T>
_インターフェイスを返し、読み取り専用です。可変リスト(つまり、ArrayList
)のクラスを直接参照する必要があります。または、ヘルパー関数arrayListOf
またはlinkedListOf
を使用して_MutableList<T>
_ 参照。それができたら、突然変異メソッドlistIterator()
を持つ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)
}
_
これにより、反復が発生するとリスト内の値が変更され、すべてのリストタイプに対して効率的です。これを簡単にするには、再利用できる便利な拡張機能を作成します(以下を参照)。
任意のMutableList
実装に対してインプレース可変反復を実行するKotlinの拡張関数を作成できます。これらのインライン関数は、イテレータのカスタム使用と同じくらい速く実行され、パフォーマンスのためにインライン化されます。 Androidまたは任意の場所に最適です。
以下にmapInPlace
拡張関数を示します(map
や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)
}
}
}
_
例この拡張機能のバリエーションを呼び出す:
_val someList = arrayListOf(1, 20, 10, 55, 30, 22, 11, 0, 99)
someList.mapInPlace { if (it <= 20) it + 20 else it }
_
ほとんどのイテレータはremove()
ではなくset()
メソッドしか持たないため、これはすべての_Collection<T>
_に対して一般化されていません。
同様の方法で汎用配列を処理できます。
_inline fun <T> Array<T>.mapInPlace(mutator: (T)->T) {
this.forEachIndexed { idx, value ->
mutator(value).let { newValue ->
if (newValue !== value) this[idx] = mutator(value)
}
}
}
_
そして、各プリミティブ配列に対して、次のバリエーションを使用します。
_inline fun BooleanArray.mapInPlace(mutator: (Boolean)->Boolean) {
this.forEachIndexed { idx, value ->
mutator(value).let { newValue ->
if (newValue !== value) this[idx] = mutator(value)
}
}
}
_
上記の拡張関数は、別のインスタンスに変更されていない場合は値を設定せず、_===
_または_!==
_の使用が Referential Equality であることを確認して、少し最適化します。 equals()
またはhashCode()
をチェックする価値はありません。これらを呼び出すとコストが不明になり、実際に参照等式が値を変更する意図をキャッチするためです。
以下に、機能する機能を示す単体テストケースと、コピーを作成するstdlib関数map()
との小さな比較を示します。
_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)
}
}
_