Edit Page

コレクションのトランスフォームオペレーション

Kotlinの標準ライブラリは、コレクションの トランスフォーム(変形) のための拡張関数を幾つか提供しています。 これらの関数は、既存のコレクションから指定されたルールに従い変形した新しいコレクションを作り出します。 このページでは、提供されているコレクションの変形関数の概要を提示します。

Map

(訳注:コレクションのマップとは別なので注意)

マッピングトランスフォームはあるコレクションの要素に対して関数を適用した結果をもとに新しいコレクションを作り出すものです。 基本的なマッピング関数としては、map()が挙げられます。 これは渡されたラムダ関数を個々の要素に適用して、その結果のリストを返します。 結果の順番は元の要素の順番と同じです。 要素の他に要素のインデックスも使う変形を行いたければ、mapIndexed()を使います。

fun main() { //sampleStart val numbers = setOf(1, 2, 3) println(numbers.map { it * 3 }) println(numbers.mapIndexed { idx, value -> value * idx }) //sampleEnd }

もし変形がある種の要素に対してnullを生成する場合、map()関数の代わりにmapNotNull()関数を使う事で、 nullを結果のコレクションから除外する事が出来ます。 mapIndexed()の代わりにも同様にmapIndexedNotNull()があります。

fun main() { //sampleStart val numbers = setOf(1, 2, 3) println(numbers.mapNotNull { if ( it == 2) null else it * 3 }) println(numbers.mapIndexedNotNull { idx, value -> if (idx == 0) null else value * idx }) //sampleEnd }

(コレクションの)マップをトランスフォームする場合は、2つの選択肢があります: 値を変更せずにキーだけをトランスフォームするか、逆にキーを変更せずに値だけをトランスフォームするかです。 キーをトランスフォームしたい場合は mapKeys()を使います。 値をトランスフォームしたい場合はmapValues()を使います。 どちらの関数もトランスフォーム関数にはマップのエントリが引数としてやってくるので、キーと値の両方を使って変換を行う事が出来ます。

fun main() { //sampleStart val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11) println(numbersMap.mapKeys { it.key.uppercase() }) println(numbersMap.mapValues { it.value + it.key.length }) //sampleEnd }

Zip

zipトランスフォーメーションは2つのコレクションの同じ位置にある要素から、それらのペアを持ったコレクションを作り出します。 Kotlinの標準ライブラリでは、これはzip()拡張関数で行う事が出来ます。

コレクションか配列に対してコレクション(か配列)を引数として指定すると、 zip()PairオブジェクトのListを返します。(訳注:わかりにくいので後の例を見ると良い) レシーバのコレクションの要素が、各ペアの最初の方の要素となります。

2つのコレクションのサイズが異なる場合、zip()の結果は小さい方のサイズとして生成されます。 長い方の残りの要素は結果には含まれません。

zip()関数は中置型(infix)でa zip bの形式で使う事も出来ます。

fun main() { //sampleStart val colors = listOf("red", "brown", "grey") val animals = listOf("fox", "bear", "wolf") println(colors zip animals) val twoAnimals = listOf("fox", "bear") println(colors.zip(twoAnimals)) //sampleEnd }

zip()に2つのパラメータを持つトランスフォーメーション関数をさらに渡す事も出来ます: 2つのパラメータはレシーバの要素と引数の要素です。 この場合、結果のListは、それぞれ同じ位置のレシーバの要素と引数の要素のペアに対して呼ばれた変形関数の結果から作られるListとなります。

fun main() { //sampleStart val colors = listOf("red", "brown", "grey") val animals = listOf("fox", "bear", "wolf") println(colors.zip(animals) { color, animal -> "The ${animal.replaceFirstChar { it.uppercase() }} is $color"}) //sampleEnd }

PairListがある場合に、逆の変換、unzipする事が出来ます。 つまり、ペアのリストから、2つのバラしたリストを作る訳です:

  • 最初のリストは各Pairの1つ目の要素からなるリスト
  • 二番目のリストはペアの2つ目の要素からなるリスト

ペアのリストをunzipするためには、unzip()関数を呼びます:

fun main() { //sampleStart val numberPairs = listOf("one" to 1, "two" to 2, "three" to 3, "four" to 4) println(numberPairs.unzip()) //sampleEnd }

Associate

Association(連想、関連付け)トランスフォーメーションはコレクションの要素から、それに関連づけした値を持つマップを作る。 associationの種類によって、要素は結果のマップのキーの方にも値の方にもなります。

基本的なassociation関数、 associateWith()は、 元のコレクションの要素がキーで、提供されるトランスフォーム関数で生成される結果が値となるMapを作りだします。 2つの等しい要素がある場合は、後に来た方だけが結果のマップに残ります。

fun main() { //sampleStart val numbers = listOf("one", "two", "three", "four") println(numbers.associateWith { it.length }) //sampleEnd }

コレクションの要素を結果のマップの値の方にするものとしては、 associateBy()関数があります。 associateBy()は引数に、要素の値を元にキーを返す関数を取ります。 もし2つの要素に対応するキーの値が等しい場合は、後に来た方だけが結果のマップに残ります。

associateBy()はvalueの方を変換する関数(valueTransform)をつけて呼ぶ事も出来ます。

fun main() { //sampleStart val numbers = listOf("one", "two", "three", "four") println(numbers.associateBy { it.first().uppercaseChar() }) println(numbers.associateBy(keySelector = { it.first().uppercaseChar() }, valueTransform = { it.length })) //sampleEnd }

キーと値の両方を渡された関数が生成するようなマップの生成方法としては、 associate()関数があります。 associate()Pairを返す関数を引数に取ります。このペアがキーと値として結果のマップのエントリになります。

associate()は短寿命のPairオブジェクトを生成するので、パフォーマンスに影響があるかもしれない事に注意しましょう。 だからassociate()の使用はパフォーマンスが問題にならないか、他の選択肢よりこちらの方が良い場合に限って使うようにしましょう。

後者の例としては、元の要素からキーと値の両方が一緒に作り出されるような場合です。

fun main() { data class FullName (val firstName: String, val lastName: String) fun parseFullName(fullName: String): FullName { val nameParts = fullName.split(" ") if (nameParts.size == 2) { return FullName(nameParts[0], nameParts[1]) } else throw Exception("Wrong name format") } //sampleStart val names = listOf("Alice Adams", "Brian Brown", "Clara Campbell") println(names.associate { name -> parseFullName(name).let { it.lastName to it.firstName } }) //sampleEnd }

ここでは要素に変形をする関数をまず適用して、その結果のプロパティからペアを作っています。

Flatten

ネストしたコレクションを操作している時には、ネストしたコレクションの要素に対するフラットなアクセスを提供する標準ライブラリを便利に使える事があるでしょう。

そのような関数の最初のものは、flatten()です。 この関数はコレクションのコレクション、例えばSetListなどに呼ぶ事が出来ます。 この関数は、ネストしたコレクションに含まれた全要素を含む単独のListを返します。

fun main() { //sampleStart val numberSets = listOf(setOf(1, 2, 3), setOf(4, 5, 6), setOf(1, 2)) println(numberSets.flatten()) //sampleEnd }

他の関数としてはflatMap()があります。 これはネストしたコレクションを処理する柔軟な方法を提供します。 flatMap()はコレクション要素を別のコレクションに変換する関数を引数に取ります。 結果としてはflatMap()は単一のリストで、その要素は返された全要素を含むものとなります。 つまりflatMap()map()関数(コレクションをコレクションに変換する)を実行したあとに、その結果にflatten()を適用する、 というように続けて2つを呼び出したかのように振る舞います。

data class StringContainer(val values: List<String>) fun main() { //sampleStart val containers = listOf( StringContainer(listOf("one", "two", "three")), StringContainer(listOf("four", "five", "six")), StringContainer(listOf("seven", "eight")) ) println(containers.flatMap { it.values }) //sampleEnd }

文字列表現

もしコレクションの内容を人間に読めるようなフォーマットで取り出したいと思うなら、 コレクションを文字列に変形するような関数を使うのが良いでしょう: joinToString()joinTo()の出番です。

joinToString()は渡された引数を元にコレクションの要素達から一つのStringを組み立てます。 joinTo()は同じ事を、渡されたAppendableオブジェクトに結果を追加してく事で行います。

デフォルトの引数で呼ぶと、この関数はコレクションのtoString()を呼んだのと似たような結果を返します: 要素の文字列表現をカンマとスペースで区切ったStringです。

fun main() { //sampleStart val numbers = listOf("one", "two", "three", "four") println(numbers) println(numbers.joinToString()) val listString = StringBuffer("数字のリスト: ") numbers.joinTo(listString) println(listString) //sampleEnd }

カスタムな文字列表現を作りたければ、関数の引数でseparator, prefix, postfixを指定する事が出来ます。 結果の文字列はprefixで始まり、postfixで終わります。 separatorは最後の要素を除いた各要素のあとに来ます。

より大きなコレクションに対しては、limitを指定したいと思うかもしれません。 これは、結果に含める総数の上限です。 もしコレクションのサイズがlimitを超えたら、それ以後のすべての要素の代わりにtruncated引数で指定された値一つに置き換わる事になります。

fun main() { //sampleStart val numbers = (1..100).toList() println(numbers.joinToString(limit = 10, truncated = "<...>")) //sampleEnd }

最後に、要素自身の表現をカスタマイズしたければ、 transform関数を渡す事が出来ます。

fun main() { //sampleStart val numbers = listOf("one", "two", "three", "four") println(numbers.joinToString { "要素: ${it.uppercase()}"}) //sampleEnd }