조용한 담장

코틀린(Kotlin) Collections : Transformations 본문

kotlin

코틀린(Kotlin) Collections : Transformations

iosroid 2020. 3. 18. 18:57

코틀린의 collection 의 transformation 에 대해 살펴보자.

원문 https://kotlinlang.org/docs/reference/collection-transformations.html 을 보며 정리.

 

한 collection 에서 다른 형태로 변형시키는 확장 함수들을 제공한다.

Mapping

다른 collection 의 element 로 부터 mapping transformation 을 만든다.

기본 함수는 map() 이다.

inline fun <T, R> Iterable<T>.map(
   transform: (T) -> R
): List<R>

주어진 람다 함수를 각 element 에 적용하여 그 결과를 가진 List 를 리턴한다.

element 의 순서는 원본의 collection 과 같다.

mapIndexed() 를 사용하면 element 의 index를 argument 로 사용할 수 있다.

inline fun <T, R> Iterable<T>.mapIndexed(
   transform: (index: Int, T) -> R
): List<R>val numbers = setOf(1, 2, 3)
println(numbers.map { it * 3 })
println(numbers.mapIndexed { idx, value -> value * idx })

// output:
// [3, 6, 9]
// [0, 2, 6]

특정 element 에서 null 이 결과로 생성되는 경우에는 map() 대신 mapNotNull(), mapIndexed() 대신 mapIndexedNotNull() 통해 걸러낼 수 있다.

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 })

// output:
// [3, 9]
// [2, 6]

map 에 적용하는 경우엔 key 나 value 중 하나에는 transformation 이 안되도록 할 수 있다.

각각 mapKeys()mapValues() 를 사용한다.

두 함수 모두 map entry 를 argument 로 사용하므로 map 의 key 와 value 모두를 이용할 수 있다.

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

// output:
// {KEY1=1, KEY2=2, KEY3=3, KEY11=11}
// {key1=5, key2=6, key3=7, key11=16}

Zipping

두개의 collection 에서 같은 위치의 element 를 각각 가져와 pair들을 만든다.

확장함수 zip() 을 사용한다.

inline fun <T, R, V> Iterable<T>.zip(
   other: Iterable<R>,
   transform: (a: T, b: R) -> V
): List<V>

zip() 은 두개의 collection 또는 array 를 argument 로 가지고 호출되어 Pair object 들을 가진 List 를 리턴한다.

receiver collection (zip()이 호출되는 대상) 의 element 들은 zip() 의 결과로 만들어진 List 의 pair 들의 첫번째 element 들이 된다.

a.zip(b) 라면 a 의 element 가 Pair 의 첫번째 element 가 되어 [(a[0], b[0]]), (a[1], b[1]), ...] 구조로 만들어 진다.

collection 의 크기가 다른 경우 작은 크기로 만들어 지게 되며 큰 collection 의 나머지 element 들은 포함이 안되게 된다.

zip() 은 infix 형태인 a zip b 로 사용될 수 있다.

infix fun <T, R> Iterable<T>.zip(
   other: Iterable<R>
): List<Pair<T, R>>
val colors = listOf("red", "brown", "grey")
val animals = listOf("fox", "bear", "wolf")
println(colors zip animals)

// different sizes
val twoAnimals = listOf("fox", "bear")
println(colors.zip(twoAnimals))

// output:
// [(red, fox), (brown, bear), (grey, wolf)]
// [(red, fox), (brown, bear)]

zip() 을 두개의 parameters 를 가지는 transformation function 과 함게 사용할 수 있다.

이 경우에 결과 List 에는 receiver 의 pairs 와 같은 위치의 elements 값의 argument 를 가지고 호출된 transformation function 의 결과 값들이 저장되게 된다.

val colors = listOf("red", "brown", "grey")
val animals = listOf("fox", "bear", "wolf")

println(colors.zip(animals) { color, animal -> "The ${animal.capitalize()} is $color"})

// output:
// [The Fox is red, The Bear is brown, The Wolf is grey]

Pair 들을 가진 List 가 있을 때 reverse transformation (unzipping) 을 통해 반대로 두개의 리스트로 분리할 수 있다.

대상 리스트의 각 Pair 의 첫번째 element 들과 두번째 element 들 만을 가진 리스트 두개로 구성된다.

unzip() 을 사용한다.

val numberPairs = listOf("one" to 1, "two" to 2, "three" to 3, "four" to 4)
println(numberPairs.unzip())

// output:
// ([one, two, three, four], [1, 2, 3, 4])

Association

Assosication transformation 은 특정 값과 연관된 element 만 사용하여 map 을 만들수 있도록 한다.

다른 association type 들에 대해서는 element 들이 key 가 되거나 value 가 될 수 있다.

associateWith() 를 사용한다.

inline fun <K, V> Iterable<K>.associateWith(
   valueSelector: (K) -> V
): Map<K, V>

collection 이 가지고 있는 기존의 값은 key 가 되고, transformation function 의 결과 들은 value 가 된다.

만약 두개의 element 동일한 경우에는 한개만 유지된다.

val numbers = listOf("one", "two", "three", "four")
println(numbers.associateWith { it.length })

// output:
// {one=3, two=3, three=5, four=4}

collection 의 element 를 value 로 사용하려면 associateBy() 를 사용한다.

inline fun <T, K> Iterable<T>.associateBy(
   keySelector: (T) -> K
): Map<K, T>
inline fun <T, K, V> Iterable<T>.associateBy(
   keySelector: (T) -> K,
   valueTransform: (T) -> V
): Map<K, V>
val numbers = listOf("one", "two", "three", "four")

println(numbers.associateBy { it.first().toUpperCase() })
println(numbers.associateBy(keySelector = { it.first().toUpperCase() }, valueTransform = { it.length }))
println(numbers.associateBy({ it.first().toUpperCase() }, { it.toUpperCase() }))

// output:
// {O=one, T=three, F=four}
// {O=3, T=5, F=4}
// {O=ONE, T=THREE, F=FOUR}

Flattening

nested(중첩) collection 을 하나의 List 로 만들어 준다.

flatten() 을 사용한다.

fun <T> Iterable<Iterable<T>>.flatten(): List<T>
val numberSets = listOf(setOf(1, 2, 3), setOf(4, 5, 6), setOf(1, 2))
println(numberSets.flatten())

// output:
// [1, 2, 3, 4, 5, 6, 1, 2]

원본 collection 에서 다른 collection 으로 매핑하는 함수를 받아 처리하는 flatMap() 을 사용하면 다양한 결과를 만들 수 있다.

val containers = listOf(
   StringContainer(listOf("one", "two", "three")),
   StringContainer(listOf("four", "five", "six")),
   StringContainer(listOf("seven", "eight"))
)
println(containers.flatMap { it.values })

// output:
// [one, two, three, four, five, six, seven, eight]

String representation

collection 의 내용을 문자열로 바꿔주는 함수로 joinToString()joinTo() 가 있다.

joinToStringString 을 리턴하고 joinToAppendable object 를 추가하여 리턴한다.

val numbers = listOf("one", "two", "three", "four")

println(numbers.toString())
println(numbers.joinToString())

val listString = StringBuffer("The list of numbers: ")
numbers.joinTo(listString)
println(listString)

// output:
// [one, two, three, four]
// one, two, three, four
// The list of numbers: one, two, three, four

separator, prefix, postfix 를 사용할 수 있다.

val numbers = listOf("one", "two", "three", "four")    
println(numbers.joinToString(separator = " | ", prefix = "start: ", postfix = ": end"))

// output:
// start: one | two | three | four: end

limit 를 사용하여 element 의 수를 제한할 수 있다.

collection 의 크기가 limit 를 넘어가면, 다른 모든 element 는 truncated argument 의 하나의 값으로 대체된다.

val numbers = (1..100).toList()
println(numbers.joinToString(limit = 10, truncated = "<...>"))

// output:
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, <...>

transform 함수를 사용하여 element 의 표현을 다양하게 할 수 있다.

val numbers = listOf("one", "two", "three", "four")
println(numbers.joinToString { "Element: ${it.toUpperCase()}"})

// output:
// Element: ONE, Element: TWO, Element: THREE, Element: FOUR


Comments