この記事では、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
)
}