Kotlinでのコレクションの変換
1. 序章
多くの場合、Kotlinコードでコレクションを操作する必要があり、多くの場合、コレクションの要素またはコレクション全体を他の形式に変換できる必要があります。 Kotlin標準ライブラリには、これを実現するためのさまざまな組み込みの方法が用意されているため、コードに集中できます。
2. 値のフィルタリング
コレクションに対して実行できるより基本的な変換の1つは、すべての要素が特定の基準を満たしていることを確認することです。 これを実行するには、あるコレクションを別のコレクションに変換し、すべての要素をフィルタリングして、一致させたい要素だけを作成します。これは、基本的に、コレクションをふるいにかけるようなものです。
基本的なfilter呼び出しは、ラムダ関数を述語として受け取り、それをコレクション内のすべての要素に適用し、述語がtrueを返す場合にのみその要素を通過させます。 :
val input = listOf(1, 2, 3, 4, 5)
val filtered = input.filter { it <= 3 }
assertEquals(listOf(1, 2, 3), filtered)
別のメソッドへの参照を含め、これに関数を提供するために任意の手段を使用できます。
val filtered = input.filter(this::isSmall)
場合によっては、これを元に戻すことができる必要があります。 1つのオプションは、呼び出しを独自のラムダでラップし、戻り値を無効にすることです。 しかし、Kotlinは、これの代わりに使用できる直接呼び出しも提供します。
val large = input.filter { !isSmall(it) }
val alsoLarge = input.filterNot(this::isSmall)
assertEquals(listOf(4, 5), filtered)
場合によっては、要素をフィルタリングするときに、コレクションへのインデックスを知る必要がある場合もあります。 このために、インデックスと要素の両方でラムダを呼び出すfilterIndexedメソッドがあります。
val input = listOf(5, 4, 3, 2, 1)
val filtered = input.filterIndexed { index, element -> index < 3 }
assertEquals(listOf(5, 4, 3), filtered)
の途中でコレクションを微妙に変換する、使用できる特別なフィルタリング方法もいくつかあります。 null許容値のコレクションを保証された非null値に変換するためのものがあり、値のコレクションをサブタイプに変換できます。
val nullable: List<String?> = ...
val nonnull: List<String> = nullable.filterNotNull()
val superclasses: List<Superclass> = ...
val subclasses: List<Subclass> = superclasses.filterIsInstance<Subclass>()
これらは、コレクションのコンテンツが何らかの要件を満たしていることを確認することにより、コレクション自体のタイプを変換していることに注意してください。
3. マッピング値
コレクションを取得して、ニーズに一致しない要素を除外する方法を見てきました。 コレクションを取得して、値を相互に変換(またはマップ)することもできます。 このマッピングは、必要に応じて単純または複雑です。
val input = listOf("one", "two", "three")
val reversed = input.map { it.reversed() }
assertEquals(listOf("eno", "owt", "eerht"), reversed)
val lengths = input.map { it.length }
assertEquals(listOf(3, 3, 5), lengths)
これらの最初のものは、Stringを別のStringに変換し、文字が逆になります。 2つ目は、 String をまったく異なるタイプに変換し、Stringの文字数を表します。 これらは両方とも、私たちのコードではまったく同じように機能します。
フィルタリングと同様に、コレクション内の要素のインデックスも認識できるバージョンがあります。
val input = listOf(3, 2, 1)
val result = input.mapIndexed { index, value -> index * value }
assertEquals(listOf(0, 2, 2), result)
場合によっては、null値を返す可能性のあるマッピング関数があり、これらを削除する必要があります。 呼び出し後にfilterNotNullをチェーンすることでこれを実行できることはすでに見てきましたが、Kotlinは実際には、mapNotNullおよびの形式でこの組み込みを提供します。 mapIndexedNotNull :
val input = listOf(1, 2, 3, 4, 5)
val smallSquares = input.mapNotNull {
if (it <= 3) {
it * it
} else {
null
}
}
assertEquals(listOf(1, 4, 9), smallSquares)
3.1. マップの変換
これまでのところ、これまで見てきたことはすべて、ListやSetなどの標準のJavaコレクションに当てはまります。 マップタイプにも特に適用できるものがいくつかあり、マップのキーまたは値を変換できます。 どちらの場合も、ラムダは次のように呼び出されます。 Map.Entry <> これはマップ内のそのエントリを表しますが、戻り値によって何が変換されるかが決まります。
val inputs = mapOf("one" to 1, "two" to 2, "three" to 3)
val squares = inputs.mapValues { it.value * it.value }
assertEquals(mapOf("one" to 1, "two" to 4, "three" to 9), squares)
val uppercases = inputs.mapKeys { it.key.toUpperCase() }
assertEquals(mapOf("ONE" to 1, "TWO" to 2, "THREE" to 3), uppercases)
4. コレクションの平坦化
個々の値をマッピングすることでコレクションを変換する方法を見てきました。 これは、ある単純な値を別の単純な値にマッピングするためによく使用されますが、代わりに単純な値を値のコレクションにマッピングするためにも使用できます。
val inputs = listOf("one", "two", "three")
val characters = inputs.map(String::toList)
assertEquals(listOf(listOf('o', 'n', 'e'), listOf('t', 'w', 'o'),
listOf('t', 'h', 'r', 'e', 'e')), characters)
必要に応じて、 flattenメソッドを使用して、これをネストされたリストではなく単一のリストに変換できます:
val flattened = characters.flatten();
assertEquals(listOf('o', 'n', 'e', 't', 'w', 'o', 't', 'h', 'r', 'e', 'e'), flattened)
これに加えて、 2つが一緒になることが多いため、flatMapメソッドがあります。 これは、単一のメソッド呼び出しに組み込まれたmapとflattenの組み合わせと考えることができます。 これに渡されるラムダがを返す必要がありますコレクション
val inputs = listOf("one", "two", "three")
val characters = inputs.flatMap(String::toList)
assertEquals(listOf('o', 'n', 'e', 't', 'w', 'o', 't', 'h', 'r', 'e', 'e'), characters)
5. コレクションを圧縮する
これまで、基準を満たさない値を除外するか、コレクション内の値を変換することによって、単一のコレクションを変換するためのいくつかのツールを見てきました。
場合によっては、2つの異なるコレクションを組み合わせて1つのコレクションを生成する変換を実行する必要があります。 これは、 zip 2つのコレクションを一緒にpingし、それぞれから要素を取得して、ペアのリストを生成することとして知られています。
val left = listOf("one", "two", "three")
val right = listOf(1, 2, 3)
val zipped = left.zip(right)
assertEquals(listOf(Pair("one", 1), Pair("two", 2), Pair("three", 3)), zipped)
新しいリストには次のものが含まれますペア
場合によっては、リストの長さが同じではありません。 この場合、結果のリストは、最短の入力リストと同じ長さになります:
val left = listOf("one", "two")
val right = listOf(1, 2, 3)
val zipped = left.zip(right)
assertEquals(listOf(Pair("one", 1), Pair("two", 2)), zipped)
これ自体がコレクションを返すため、フィルタリングやマッピングなど、追加の変換を実行できます。 これはに作用しますペア
val posts = ...
posts.map(post -> authorService.getAuthor(post.author)) // Returns a collection of authors
.zip(posts) // Returns a collection of Pair<Author, Post>
.map((author, post) -> "Post ${post.title} was written by ${author.name}")
その後、別の方向に進む必要がある場合があります。 コレクション 、リスト
:
val left = listOf("one", "two", "three")
val right = listOf(1, 2, 3)
val zipped = left.zip(right)
val (newLeft, newRight) = zipped.unzip()
assertEquals(left, newLeft)
assertEquals(right, newRight)
6. コレクションをマップに変換する
これまで見てきたことはすべて、コレクションを同じタイプに変換し、コレクション内のデータを操作するだけです。 コレクションをに変換することもできます地図
最も簡単な方法は、 toMap() 直接変換する方法コレクション
val input = listOf(Pair("one", 1), Pair("two", 2))
val map = input.toMap()
assertEquals(mapOf("one" to 1, "two" to 2), map)
どうやって私たちを得るかコレクション
コレクション内の値をどこかからの他の値に関連付けることで、これの一部を一緒に実行することもできます。ここには、マップキー、マップ値などのコレクション要素を処理するためのオプションがあります。キーと値の両方を生成します。
val inputs = listOf("Hi", "there")
// Collection elements as keys
val map = inputs.associateWith { k -> k.length }
assertEquals(mapOf("Hi" to 2, "there" to 5), map)
// Collection elements as values
val map = inputs.associateBy { v -> v.length }
assertEquals(mapOf(2 to "Hi", 5 to "there"), map)
// Collection elements generate key and value
val map = inputs.associate { e -> Pair(e.toUpperCase(), e.reversed()) }
assertEquals(mapOf("HI" to "iH", "THERE" to "ereht"), map)
この場合、重複は除外されます。 同じマップキーを生成するコレクション内の要素は、最後の1つだけをマップに表示します。
val inputs = listOf("one", "two")
val map = inputs.associateBy { v -> v.length }
assertEquals(mapOf(3 to "two"), map)
代わりにすべての複製を保持して、一緒にマップされたすべてのインスタンスを認識したい場合は、代わりにgroupByメソッドを使用できます。 これらは地図
val inputs = listOf("one", "two", "three")
val map = inputs.groupBy { v -> v.length }
assertEquals(mapOf(3 to listOf("one", "two"), 5 to listOf("three")), map)
7. コレクションを文字列に結合する
別のコレクションやマップに変換する代わりに、コレクション内のすべての要素を1つの単一の文字列に結合することができます。
これを行うとき、オプションで、要素間の区切り文字、新しい文字列の先頭のプレフィックス、および文字列の末尾のサフィックスに使用する値を指定できます。
val inputs = listOf("Jan", "Feb", "Mar", "Apr", "May")
val simpleString = inputs.joinToString()
assertEquals("Jan, Feb, Mar, Apr, May", simpleString)
val detailedString = inputs.joinToString(separator = ",", prefix="Months: ", postfix=".")
assertEquals("Months: Jan,Feb,Mar,Apr,May.", detailedString)
ご覧のとおり、接頭辞と接尾辞を省略すると、それらは空の文字列になり、区切り文字を省略すると、文字列「、」になります。
組み合わせる要素の数に制限を指定することもできます。 これを行うとき、文字列を制限する場合に付ける切り捨てサフィックスを追加で指定できます。
val inputs = listOf("Jan", "Feb", "Mar", "Apr", "May")
val simpleString = inputs.joinToString(limit = 3)
assertEquals("Jan, Feb, Mar, ...", simpleString)
この呼び出しには、要素を変換する機能も組み込まれています。 これは、joinToString呼び出しの前にmap呼び出しを行った場合とまったく同じように機能しますが、実際に含まれている要素、つまり[ X213X] limit 呼び出しは変換されません:
val inputs = listOf("Jan", "Feb", "Mar", "Apr", "May")
val simpleString = inputs.joinToString(transform = String::toUpperCase)
assertEquals("JAN, FEB, MAR, APR, MAY", simpleString)
出力文字列をAppendableインスタンスに書き込むこの呼び出しの追加バージョン( StringBuilder など)にもアクセスできます。 これにより、作成中の文字列の途中にコレクションを結合できます。基本的には、プレフィックスおよびサフィックスパラメーターのより強力なバージョンとして次のようになります。
val inputs = listOf("Jan", "Feb", "Mar", "Apr", "May")
val output = StringBuilder()
output.append("My ")
.append(inputs.size)
.append(" elements: ")
inputs.joinTo(output)
assertEquals("My 5 elements: Jan, Feb, Mar, Apr, May", output.toString())
8. コレクションを値に減らす
上記では、コレクションを1つの値に結合する特定のアクションを実行する方法、つまり値をすべて1つの文字列に結合する方法を説明しました。
コレクションの削減として知られる、使用できるはるかに強力な代替手段があります。 これは、コレクション内の次の要素をこれまでに累積された値に結合する方法を知っているラムダを提供することによって機能します。 最後に、合計累積値を取得します。
たとえば、この機能を使用して、joinToStringの単純なバージョンを実装できます。
val inputs = listOf("Jan", "Feb", "Mar", "Apr", "May")val result =
inputs.reduce { acc, next -> "$acc, $next" }
assertEquals("Jan, Feb, Mar, Apr, May", result)
これは、配列の最初の要素を初期累積値として使用し、ラムダを呼び出して後続の各値を結合します。
- 1を呼び出す– acc =“ Jan”、next =“ Feb”
- 電話2– acc =“ Jan、Feb”、next =“ Mar”
- 電話3– acc =「1月、2月、3月」、次=「4月」
- 電話4– acc =「1月、2月、3月、4月」、次=「5月」
これは、要素が目的の出力と同じタイプである場合にのみ機能します。 また、少なくとも1つの要素を含むコレクションに対してのみ機能します。
もう1つの方法は、foldメソッドです。これはほとんど同じですが、初期累積値として追加の引数を提供します。 これにより、累積値を任意のタイプにすることができ、空のコレクションを含む任意のサイズのコレクションをサポートできます。
val inputs = listOf("Jan", "Feb", "Mar", "Apr", "May")
val result = inputs.fold(0) { acc, next -> acc + next.length }
assertEquals(15, totalLength)
今回、ラムダは5回(リスト内の要素ごとに1回)呼び出され、初期累積値は0になります。
9. 概要
さまざまな方法でコレクションを操作するために、Kotlin標準ライブラリに組み込まれているさまざまなツールを見てきました。 ここに表示されているすべての例は、GitHubのにあります。 次のプロジェクトでこれらをどのように活用できるか見てみませんか?