컬렉션 변환 연산
Kotlin 표준 라이브러리는 컬렉션 _변환_을 위한 확장 함수 세트를 제공합니다. 이 함수들은 제공된 변환 규칙에 따라 기존 컬렉션에서 새로운 컬렉션을 구성합니다. 이 페이지에서는 사용 가능한 컬렉션 변환 함수에 대한 개요를 제공합니다.
맵
매핑 변환은 다른 컬렉션의 요소에 대한 함수의 결과로부터 컬렉션을 생성합니다. 기본 매핑 함수는 map()
입니다. 이 함수는 주어진 람다 함수를 각 후속 요소에 적용하고 람다 결과 목록을 반환합니다. 결과의 순서는 원래 요소의 순서와 동일합니다. 요소 인덱스를 인수로 추가 사용하는 변환을 적용하려면 mapIndexed()
를 사용합니다.
fun main() {
val numbers = setOf(1, 2, 3)
println(numbers.map { it * 3 })
println(numbers.mapIndexed { idx, value -> value * idx })
}
변환이 특정 요소에 대해 null
을 생성하는 경우, map()
대신 mapNotNull()
함수를 호출하거나, mapIndexed()
대신 mapIndexedNotNull()
를 호출하여 결과 컬렉션에서 null
을 필터링할 수 있습니다.
fun main() {
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 })
}
맵을 변환할 때는 두 가지 옵션이 있습니다: 값을 변경하지 않고 키를 변환하거나 그 반대로 할 수 있습니다. 주어진 변환을 키에 적용하려면 mapKeys()
를 사용하고, 반대로 mapValues()
는 값을 변환합니다. 두 함수 모두 맵 엔트리를 인수로 받는 변환을 사용하므로, 키와 값 모두를 조작할 수 있습니다.
fun main() {
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 })
}
Zip
Zipping 변환은 두 컬렉션에서 동일한 위치에 있는 요소들로 쌍을 구성하는 것입니다. Kotlin 표준 라이브러리에서는 zip()
확장 함수를 통해 이 작업이 수행됩니다.
다른 컬렉션(또는 배열)을 인수로 사용하여 컬렉션 또는 배열에서 호출될 때, zip()
은 Pair
객체들의 List
를 반환합니다. 수신 컬렉션의 요소들은 이 쌍들에서 첫 번째 요소가 됩니다.
컬렉션의 크기가 다를 경우, zip()
의 결과는 더 작은 크기가 됩니다; 더 큰 컬렉션의 마지막 요소들은 결과에 포함되지 않습니다.
zip()
은 또한 중위(infix) 형식인 a zip b
로 호출될 수 있습니다.
fun main() {
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))
}
두 개의 매개변수(수신 요소와 인수 요소)를 받는 변환 함수와 함께 zip()
을 호출할 수도 있습니다. 이 경우, 결과 List
는 동일한 위치에 있는 수신 요소와 인수 요소의 쌍에 대해 호출된 변환 함수의 반환 값을 포함합니다.
fun main() {
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"})
}
Pair
들의 List
를 가지고 있을 때, 이 쌍들로부터 두 개의 리스트를 구성하는 역변환, 즉 _압축 해제(unzipping)_를 수행할 수 있습니다:
- 첫 번째 리스트는 원래 리스트에 있는 각
Pair
의 첫 번째 요소를 포함합니다. - 두 번째 리스트는 두 번째 요소들을 포함합니다.
쌍 리스트를 압축 해제하려면 unzip()
를 호출합니다.
fun main() {
val numberPairs = listOf("one" to 1, "two" to 2, "three" to 3, "four" to 4)
println(numberPairs.unzip())
}
Associate
연관(Association) 변환은 컬렉션 요소와 그와 연관된 특정 값들로부터 맵을 구성할 수 있게 합니다. 다양한 연관 타입에서, 요소들은 연관 맵의 키 또는 값이 될 수 있습니다.
기본 연관 함수인 associateWith()
는 원래 컬렉션의 요소들이 키가 되고, 주어진 변환 함수에 의해 그 요소들로부터 값이 생성되는 Map
을 생성합니다. 두 요소가 같으면 마지막 요소만 맵에 남습니다.
fun main() {
val numbers = listOf("one", "two", "three", "four")
println(numbers.associateWith { it.length })
}
컬렉션 요소를 값으로 사용하여 맵을 구성하려면 associateBy()
함수가 있습니다. 이 함수는 요소의 값을 기반으로 키를 반환하는 함수를 인수로 받습니다. 두 요소의 키가 같으면 마지막 요소만 맵에 남습니다.
associateBy()
는 값 변환 함수와 함께 호출될 수도 있습니다.
fun main() {
val numbers = listOf("one", "two", "three", "four")
println(numbers.associateBy { it.first().uppercaseChar() })
println(numbers.associateBy(keySelector = { it.first().uppercaseChar() }, valueTransform = { it.length }))
}
키와 값 모두가 컬렉션 요소로부터 어떤 방식으로든 생성되는 맵을 구성하는 또 다른 방법은 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")
}
val names = listOf("Alice Adams", "Brian Brown", "Clara Campbell")
println(names.associate { name -> parseFullName(name).let { it.lastName to it.firstName } })
}
여기서는 먼저 요소에 변환 함수를 호출하고, 그 함수의 결과 속성에서 쌍을 구성합니다.
Flatten
중첩된 컬렉션을 다룰 경우, 중첩된 컬렉션 요소에 대한 플랫 액세스를 제공하는 표준 라이브러리 함수가 유용하다는 것을 알 수 있습니다.
첫 번째 함수는 flatten()
입니다. 예를 들어, Set
들의 List
와 같이 컬렉션들의 컬렉션에서 이 함수를 호출할 수 있습니다. 이 함수는 중첩된 컬렉션의 모든 요소를 포함하는 단일 List
를 반환합니다.
fun main() {
val numberSets = listOf(setOf(1, 2, 3), setOf(4, 5, 6), setOf(1, 2))
println(numberSets.flatten())
}
또 다른 함수인 flatMap()
는 중첩된 컬렉션을 처리하는 유연한 방법을 제공합니다. 이 함수는 컬렉션 요소를 다른 컬렉션으로 매핑하는 함수를 인수로 받습니다. 결과적으로 flatMap()
은 모든 요소에 대한 반환 값들의 단일 리스트를 반환합니다. 따라서 flatMap()
은 map()
(매핑 결과로 컬렉션을 사용하는)과 flatten()
을 연속적으로 호출하는 것처럼 동작합니다.
data class StringContainer(val values: List<String>)
fun main() {
val containers = listOf(
StringContainer(listOf("one", "two", "three")),
StringContainer(listOf("four", "five", "six")),
StringContainer(listOf("seven", "eight"))
)
println(containers.flatMap { it.values })
}
문자열 표현
컬렉션 내용을 읽기 쉬운 형식으로 검색해야 하는 경우, 컬렉션을 문자열로 변환하는 함수인 joinToString()
와 joinTo()
를 사용합니다.
joinToString()
은 제공된 인수를 기반으로 컬렉션 요소로부터 단일 String
을 구성합니다. joinTo()
는 동일한 작업을 수행하지만, 결과를 주어진 Appendable
객체에 추가합니다.
매개변수의 기본값으로 호출될 때, 이 함수들은 컬렉션에 toString()
을 호출하는 것과 유사한 결과를 반환합니다: 요소들의 문자열 표현이 공백과 쉼표로 구분된 String
입니다.
fun main() {
val numbers = listOf("one", "two", "three", "four")
println(numbers)
println(numbers.joinToString())
val listString = StringBuffer("The list of numbers: ")
numbers.joinTo(listString)
println(listString)
}
사용자 지정 문자열 표현을 구성하려면, 함수 인수인 separator
, prefix
, postfix
에 해당 매개변수를 지정할 수 있습니다. 결과 문자열은 prefix
로 시작하여 postfix
로 끝납니다. separator
는 마지막 요소를 제외한 각 요소 뒤에 옵니다.
fun main() {
val numbers = listOf("one", "two", "three", "four")
println(numbers.joinToString(separator = " | ", prefix = "start: ", postfix = ": end"))
}
더 큰 컬렉션의 경우, 결과에 포함될 요소의 수인 limit
를 지정하고 싶을 수 있습니다. 컬렉션 크기가 limit
를 초과하면, 다른 모든 요소는 truncated
인수의 단일 값으로 대체됩니다.
fun main() {
val numbers = (1..100).toList()
println(numbers.joinToString(limit = 10, truncated = "<...>"))
}
마지막으로, 요소 자체의 표현을 사용자 지정하려면 transform
함수를 제공하십시오.
fun main() {
val numbers = listOf("one", "two", "three", "four")
println(numbers.joinToString { "Element: ${it.uppercase()}"})
}