Kotlin 覚えておきたいCollection操作

この記事では、Kotlinのリストなどのコレクション操作について紹介していきます。

よく使うものや、意外と知らない操作関数もあるかと思うので、
コレクション操作に迷ったら、参考にしていただけると幸いです。

Kotlinでのコレクション操作を各分類ごとにまとめています。

判定系 predicating

any, none, all

  • any
    一つでも一致すればtrue
  • none
    一つも一致しなければtrue
  • all
    すべてが一致すればtrue
data class Person(
    val name: String,
    val age: Int,
)

@Test
fun `test predicate`() {

    val list = listOf(
        Person("Jan", 32),
        Person("Joseph", 20),
        Person("Tommy", 19),
        Person("Alexander", 40),
    )

    // 20歳未満が一人でもいればtrue
    Assertions.assertTrue(list.any { it.age < 20 })

    // 全員10歳未満でなければ、true
    Assertions.assertTrue(list.none { it.age < 10 })

    // 全員18歳以上であれば、true
    Assertions.assertTrue(list.all { it.age >= 18 })
}

フィルタ系 filtering

filter, find, findLast

  • filter
    指定の条件がtrueの要素だけを返す
  • find
    指定の条件がtrueの先頭要素だけを返す
  • findLast
    指定の条件がtrueの末尾要素だけを返す
@Test
fun `test filter`() {

    val list = listOf(
        Person("Jan", 32),
        Person("Joseph", 20),
        Person("Tommy", 19),
        Person("Alexander", 40),
    )

    // 20歳以下を取得
    val filteredList = list.filter { it.age <= 20 }
    Assertions.assertEquals(listOf("Joseph", "Tommy"), filteredList.map { it.name })

    // 最初に見つかった要素を取得 (ない場合はnullを返す)
    val person1: Person? = list.find { it.age >= 40 }
    Assertions.assertNotNull(person1)
    Assertions.assertEquals("Alexander", person1?.name)

    // 条件に一致する最後の要素を取得 (ない場合はnullを返す)
    val person2 = list.findLast { it.age >= 20 }
    Assertions.assertNotNull(person2)
    Assertions.assertEquals("Alexander", person2?.name)
}

minByOrNull, maxByOrNull

  • minByOrNull
    コレクションの中で、一番小さい要素を取得
  • maxByOrNull
    コレクションの中で、一番大きい要素を取得
@Test
fun `test min and max`() {

    val list = listOf(
        Person("Jan", 32),
        Person("Joseph", 20),
        Person("Tommy", 19),
        Person("Alexander", 40),
    )

    // 最年少の取得 (要素がない場合はnullを返す)
    val min = list.minByOrNull { it.age }

    // 最年長を取得 (要素がない場合はnullを返す)
    val max = list.maxByOrNull { it.age }

    Assertions.assertEquals(19, min?.age)
    Assertions.assertEquals(40, max?.age)
}

distinct

  • distinct
    重複要素を除去する。
@Test
fun `test distinct`() {
    val list = listOf(1, 2, 3, 1, 2, 3, 4, 5)

    // 重複を除去したリストを返す。
    Assertions.assertEquals(listOf(1, 2, 3, 4, 5), list.distinct())
}

変換系 mapping

map, flatMap

  • map
    要素の別のものに変換
  • flatMap
    要素を複数のものに変換
@Test
fun `test mapper`() {

    val list = listOf(
        Person("Jan", 32),
        Person("Joseph", 20),
        Person("Tommy", 19),
    )

    // 要素を別の要素に変換
    val names = list.map { it.name }
    val ages = list.map { it.age }
    Assertions.assertEquals(listOf("Jan", "Joseph", "Tommy"), names)
    Assertions.assertEquals(listOf(32, 20, 19), ages)

    // 要素を複数の要素に変換
    val chars = list.flatMap { it.name.toList() }
    Assertions.assertEquals(listOf('J', 'a', 'n', 'J', 'o', 's', 'e', 'p', 'h', 'T', 'o', 'm', 'm', 'y'), chars)
}

zip, zipWithNext

  • zip
    2つのコレクションから、それぞれ要素を取り出し、1つの要素に変換。
  • zipWithNext
    1つのコレクションの、隣り合う要素同士を1つの要素に変換。
@Test
fun `test zip`() {
    val list1 = listOf("Jan", "Joseph", "Tommy", "Alexander")
    val list2 = listOf(32, 20, 19)

    // 2つのコレクションから要素を別の要素に変換
    // list1.zip(list2, ::Person) でもOK
    val persons = list1.zip(list2) { a, b -> Person(a, b) }

    // 要素数の少ない方に合わせられるので、"Alexander" の要素は含まれない。
    Assertions.assertEquals(
        listOf(
            Person("Jan", 32),
            Person("Joseph", 20),
            Person("Tommy", 19),
        ),
        persons
    )

    // 隣同士の要素でzip処理する。
    val zippedList = listOf('a', 'b', 'c', 'd', 'e')
        .zipWithNext { a, b -> "${a.uppercase()}${b.uppercase()}" }
    Assertions.assertEquals(listOf("AB", "BC", "CD", "DE"), zippedList)
}

再編成系 picking up

take, takeLast

  • take
    指定の個数だけ先頭から要素を取得
  • takeLast
    指定の個数だけ末尾から要素を取得
@Test
fun `test take`() {

    val list = listOf(
        Person("Jan", 32),
        Person("Joseph", 20),
        Person("Tommy", 19),
        Person("Alexander", 40),
    )

    // 先頭から3要素を取得
    val list1 = list.take(3)
    Assertions.assertEquals(listOf("Jan", "Joseph", "Tommy"), list1.map { it.name })

    // 末尾から3要素を取得
    val list2 = list.takeLast(3)
    Assertions.assertEquals(listOf("Joseph", "Tommy", "Alexander"), list2.map { it.name })
}

chunked

  • chunked
    指定の個数ごとに要素をまとめる
@Test
fun `test chunk`() {
    val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)

    // 指定の個数毎にリスト化する
    val chunkedList = list.chunked(4)

    Assertions.assertEquals(
        listOf(
            listOf(1, 2, 3, 4),
            listOf(5, 6, 7, 8),
            listOf(9),
        ),
        chunkedList
    )
}

windowed

  • windowed
    指定の個数ごとにシフトして要素をまとめる
    窓がスライドしていくようなイメージで捉えるとわかりやすい。
@Test
fun `test window`() {
    val list = listOf(1, 2, 3, 4, 5)

    // 3要素の窓がスライドしていくようにリスト化する。
    val windowedList1 = list.windowed(3)

    Assertions.assertEquals(
        listOf(
            listOf(1, 2, 3),
            listOf(2, 3, 4),
            listOf(3, 4, 5)
        ),
        windowedList1
    )

    // 3要素の窓が、2個ずつスライドしていくようにリスト化する。
    val windowedList2 = list.windowed(3, step = 2)

    Assertions.assertEquals(
        listOf(
            listOf(1, 2, 3),
            listOf(3, 4, 5),
        ),
        windowedList2
    )
}

分類系 grouping

partition, groupBy

  • partition
    2つのリストに分類する。
  • groupBy
    指定のキーで分類する。
@Test
 fun `test grouping`() {

     val list = listOf(
         Person("Jan", 32),
         Person("Joseph", 20),
         Person("Tommy", 19),
         Person("Alexander", 40),
     )

     // 30未満とそうじゃないグループに分ける。(返り値はPairで返る)
     // first が、条件trueになった方
     // second が、条件trueになった方
     val (first, second) = list.partition { it.age < 30 }

     Assertions.assertEquals(listOf("Joseph", "Tommy"), first.map { it.name })
     Assertions.assertEquals(listOf("Jan", "Alexander"), second.map { it.name })

     // イニシャルでグルーピングする。
     // Map<Char, List<Person>>
     // グルーピングするためのキーを指定
     val map = list.groupBy { it.name[0] }

     Assertions.assertEquals(listOf("Jan", "Joseph"), map['J']?.map { it.name })
     Assertions.assertEquals(listOf("Tommy"), map['T']?.map { it.name })
     Assertions.assertEquals(listOf("Alexander"), map['A']?.map { it.name })
 }

集約系 reducing

sum, average

  • sum
    コレクションすべてのプラス演算の結果を返す。
  • average
    sumの結果を、要素数で割った値を返す。
@Test
fun `test sum and average`() {
    val list = listOf(1, 2, 3, 1, 2, 3)

    // 要素の合計値
    Assertions.assertEquals(12, list.sum())

    // 平均値
    Assertions.assertEquals(2.0, list.average())
}

reduce, fold

  • reduce
    accumeratorを使った集約、最初の要素が初期値。
  • fold
    accumeratorを使った集約、初期値を指定できる。
@Test
fun `test reducing`() {
    val list = listOf(1, 2, 3, 1, 2, 3)

    // accには最初の要素、次からは関数が返した値が渡ってくる。
    // reduceを使った合計値の算出
    val sum = list.reduce { acc, value -> acc + value }
    Assertions.assertEquals(12, sum)

    // accには最初の要素ではなく、指定の初期値から始まる。次からは関数が返した値が渡ってくる。
    // 要素の値に応じて矢印を作成
    val ret = list.fold("") { acc, value -> acc + "-".repeat(value) + ">" }
    Assertions.assertEquals("->-->--->->-->--->", ret)
}

runningReduce, runningFold

  • runningReduce
    accumeratorが返した値をすべてリストに納めつつ集約する、最初の要素が初期値。
  • runningFold
    accumeratorが返した値をすべてリストに納めつつ集約する、初期値を指定できる。
@Test
fun `test running reduce and fold`() {
    val list = listOf(1, 2, 3, 1, 2, 3)

    // 関数が返した結果を要素としてリストを返す。
    // リストの先頭には、最初の要素、そのあとは関数が返した値が入る。
    val list2 = list.runningReduce { acc, value -> acc + value }
    Assertions.assertEquals(listOf(1, 3, 6, 7, 9, 12), list2)

    // accには最初の要素ではなく、指定の初期値から始まる。次からは関数が返した値が渡ってくる。
    // リストの先頭には、指定の値、そのあとは関数が返した値が入る。
    // 要素の値に応じて矢印を作成
    val list3 = list.runningFold("") { acc, value -> acc + "-".repeat(value) + ">" }
    Assertions.assertEquals(
        listOf("", "->", "->-->", "->-->--->", "->-->--->->", "->-->--->->-->", "->-->--->->-->--->"),
        list3
    )
}

コメント

タイトルとURLをコピーしました