🗂

kotlin.collectionsの紹介〜初級編〜

2021/12/14に公開

こんにちは、株式会社イエソドのtsukakeiです

これはKotlin Advent Calendar 2021 15日目の記事です

みなさん、Kotlinは好きですか?私は大好きです!

Kotlinではコレクション系の便利メソッドを標準ライブラリとして提供してくれています

前回は初心者向けのメソッドを紹介しましたが、今回は初級編です

allanycount

どんなときに使う?

コレクションに対してforEachしたくなったとき

allは、渡したpredicateがコレクションの全要素に対して、trueかどうかを返します

val isEven: (Int) -> Boolean = { it % 2 == 0 }
val zeroToTen = 0..10

val allEven: Boolean = zeroToTen.all { isEven(it) } // false

anyは、渡したpredicateがコレクションのある要素に対して、trueになるものがあるかどうかを返します

val isEven: (Int) -> Boolean = { it % 2 == 0 }
val zeroToTen = 0..10

val anyEven: Boolean = zeroToTen.any { isEven(it) } // true

countは、渡したpredicateがコレクションのある要素に対して、trueになる要素の個数を返します

val isEven: (Int) -> Boolean = { it % 2 == 0 }
val zeroToTen = 0..10

val numOfEven: Int = zeroToTen.count { isEven(it) } // 6

filterNotNull

どんなときに使う?

コレクションからnullを取り除いてほしいとき

filterNotNullは、コレクションの各要素のうち、nullでない要素のみを収納したListを返します

kotlin
fun main() {
  val numbers: List<Int?> = listOf(1, 2, null, 4)
  val nonNullNumbers = numbers.filterNotNull()

  println(nonNullNumbers) // [1, 2, 4]
}

filter { it != null } でも同じ挙動にはなりますが、意図が伝わりやすい & 型からNullableが取り除かれるので、nullを取り除きたい用途であればfilterNotNullを使った方がいいです

kotlin
val numbers: List<Int?> = listOf(1, 2, null, 4)

val nonNullNumbers: List<Int> = numbers.filterNotNull() // good
val maybeNonNullNumbers: List<Int?> = numbers.filter { it != null } // bad

mapNotNull

どんなときに使う?

コレクションを変換して、その結果からnullを取り除いてほしいとき

mapNotNullは、渡したtransformをコレクションの各要素に対して実行し、nullを除いた結果を収納したListを返します

fun main() {
  val strings: List<String> = listOf("12a", "45", "", "3")
  val ints: List<Int> = strings.mapNotNull { it.toIntOrNull() }
  println(ints) // [45, 3]
}

map { ... }.filterNotNull() でも同じ結果にはなりますが、取り除きたいものはわざとnullにしているという意図が伝わりやすい & 短くて読みやすくなるので、mapNotNullを使った方がいいです

kotlin
val strings: List<String> = listOf("12a", "45", "", "3")

val ints1: List<Int> = strings.mapNotNull { it.toIntOrNull() } // better
val ints2: List<Int> = strings.map { it.toIntOrNull() }.filterNotNull() // good

findfindLast

どんなときに使う?

コレクションから要素を探すとき

findは、渡したpredicateをコレクションの 先頭 から各要素に対して実行し、最初にtrueになった要素(trueになる要素がなかった場合はnull)を返します
findLastは、渡したpredicateをコレクションの 最後 から各要素に対して実行し、最初にtrueになった要素(trueになる要素がなかった場合はnull)を返します

fun main() {
  val numbers = listOf(1, 2, 3, 4, 5, 6, 7)
  val firstOdd = numbers.find { it % 2 != 0 }
  val lastEven = numbers.findLast { it % 2 == 0 }

  println(firstOdd) // 1
  println(lastEven) // 6
}

似たようなメソッド(というか実装は同じ)としては、firstOrNull(predicate: (T) -> Boolean)lastOrNull(predicate: (T) -> Boolean)があります

fun main() {
  val numbers = listOf(1, 2, 3, 4, 5, 6, 7)
  val firstOdd = numbers.firstOrNull { it % 2 != 0 }
  val lastEven = numbers.lastOrNull { it % 2 == 0 }

  println(firstOdd) // 1
  println(lastEven) // 6
}

associate系(associateassociateByassociateWith

どんなときに使う?

コレクションからMapを作りたいとき

コレクションからMapを生成したいときはままあるかと思います(計算量減らしたい時とか)
そのときに便利なのがassociate系のメソッドです

associate系にはassociateassociateByassociateWithの3種類ありますが、その使い分けは簡単です

コレクションの各要素から、

  1. MapのKeyもValueも作りたい → associate
  2. MapのKeyだけ作りたい → associateBy
  3. MapのValueだけ作りたい → associateWith

となります

MapのKeyもValueも作りたい → associate

kotlin
fun main() {
  val names = listOf("田中 太郎", "鈴木 次郎", "佐藤 太郎")
  val byLastName = names.associate { it.split(" ").let { (firstName, lastName) -> lastName to firstName } }

  // "田中 太郎"は、あとの"佐藤 太郎"とkeyかぶりしたので上書きされて出てきません
  println(byLastName) // {次郎=鈴木, 太郎=佐藤}
}

Keyだけ作りたい → associateBy

fun main() {
  data class Person(val firstName: String, val lastName: String)

  val names = listOf(Person("田中", "太郎"), Person("鈴木", "次郎"), Person("佐藤", "太郎"))
  val byLastName = names.associateBy { it.lastName }

  // "田中 太郎"は、あとの"佐藤 太郎"とkeyかぶりしたので上書きされて出てきません
  println(byLastName) // {次郎=鈴木 次郎, 太郎=佐藤 太郎}
}

ちなみに、valueTransformを指定することも一応できます

MapのValueだけ作りたい → associateWith

kotlin
fun main() {
  val words = listOf("a", "abc", "ab", "def", "abcd")
  val withLength = words.associateWith { it.length }
  println(withLength) // {a=1, abc=3, ab=2, def=3, abcd=4}
}

おわりに

今回は初級編ということで、基本的なメソッドを中心に取り上げてみました!
(associate系については、中級編と初級編の中間くらいで迷いましたが入れました)

次回は中級編をお届けしたいと思います
ありがとうございました!

Discussion