코틀린 언어 정리 4-6

Collection operations

Common operations( 일반적인 연산들 )

Grouping

같은 값의 element를 모아 하나로 줄입니다. 좀 더 구체적으로는, Array계열의 collection(List 포함)등에서 중복되는 요소들을 한개의 요소로 축약하여 Map으로 만드는 연산입니다. 대표적으로 groupBy와 groupingBy 연산이 있습니다. groupBy는 호출하면 바로 Map<K, List<T>>을 생성하고 groupingBy는 Grouping<T,K> 타입의 중간 단계의 객체를 생성합니다. 이 객체에서는 reduce, fold, aggregate등의 연산을 지원합니다. 이 연산들은 Map의 각 요소의 key, value(List<T>) 에서 value(List<T>)에 대해 적용됩니다.

Grouping연산은, Map을 생성하면서 축약된 요소(key)에 대응하는 각각의 값 들(value list)에 대해 누적 연산(reduce, fold, aggregate,...)등을 적용할 때 유용하게 사용할 수 있습니다.

 

groupBy ( keySelector, valueTransform ), groupingBy ( keySelector ): Grouping<>

Array or List 등에서의 reduce, fold

reduce

inline fun <S, T : S> Array<out T>.reduce(operation: (accumulator: S, element: T) -> S): S

 

element에 대한 임의의 연산 결과를 accumulator에 저장하여 리턴하고 다음 element에 대해 현재까지의 accumulator 값을 이용하여 동일한 연산을 다시 수행합니다. 이와 같은 과정을 모든 element에 대해 반복합니다.

accumulator의 초기 값은 첫번째 element입니다.

 

fold

inline fun <T, R> Array<out T>.fold(initial: R, operation: (accumulator: R, element: T) -> R): R

reduce와 동일한 기능이지만 처음 fold 연산을 시작할 때 accumulator의 초기값을 fold 함수 매개변수(initial)를 통해 지정할 수 있습니다.

 

groupBy, groupingBy

groupBy와 groupingBy를 비교하면, groupBy 연산은 후속 연산 없이 Map<K, List<T>>를 바로 생성하고 groupingBy 연산은 Grouping<T, K>를 생성한 후 이를 통해 Map의 value에 해당되는 List<T>에 aggregate, fold, reduce, eachCount등의 연산을 수행하여 Map<K, R>을 생성합니다. (K=key, T=element, R=key에 대응하는 List<T>에 대해 reduce, fold, aggregate 등과 같은 통합 연산을 적용한 결과)

groupingBy를 호출하여 Grouping<T,K>타입의 객체를 생성하면 현재 key에 Mapping되어 있는 value(Map의 각 요소에 대응하는 collection타입의 value)에 대해 누적 연산을 적용하여 새로운 Map에 저장할 수 있습니다.

 

Grouping<T, K>

groupingBy에서 리턴하는 Grouping<T, K>타입에서는 집계(누적)연산을 제공합니다. Array, List 등에서 제공되는 연산과 비슷하지만, Grouping<T, K>에서 제공되는 함수에는 첫번째 parameter에 key 가 추가되어 있습니다. 

동작의 차이를 비교해 보면, Array, List등에서 제공되는 연산은 단지 리시버 객체(Array, List)에 연산이 적용된 후 단일 값이 리턴 되고, Grouping<T,K>에서 제공되는 연산은 groupBy에 의해 생성되는 Map 유형(Map<K, List<T>>)에서 List<T>에 해당되는, 즉 모든 key에 대응하는 value list에 집계 연산이 적용되어 최종적으로는 Map<K,T> 타입의(key, value Pair) Map이 생성됩니다. 

 

aggregate

inline fun <T, K, R> Grouping<T, K>.aggregate(operation: (key: K, accumulator: R?, element: T, first: Boolean) -> R): Map<K, R>

reduce

inline fun <S, T : S, K> Grouping<T, K>.reduce(operation: (key: K, accumulator: S, element: T) -> S): Map<K, S>

fold

inline fun <T, K, R> Grouping<T, K>.fold(initialValueSelector: (key: K, element: T) -> R, operation: (key: K, accumulator: R, element: T) -> R): Map<K, R>

eachCount

Map의 key에 몇 개의 요소가 그룹화 되었는지 리턴합니다.

 

reduce, fold, aggregate 함수 선택 기준

reduce, fold, aggregate 함수는 큰 흐름에서 모두 동일한 연산을 수행 하지만 초기값 연산, accumulator 할당 시점 제어 연산과 관련하여 차이가 있습니다. 초기값 및 초기값이 발견되는 시점에 특별한 작업을 할 필요가 없을 경우 reduce, 그렇지 않을 경우 fold, 이에 추가로 accumulator의 할당까지 제어하려면 aggregate를 사용하면 됩니다.

 

예제

//fun main ( args: Array<String> ) {
fun test () {
   /////////////////////////////////////
   // Array<out T> 계열의 fold and reduce
   var list = listOf("개", "고양이", "돼지", "소", "호랑이", "개", "고양이", "곰", "개")
   var result_list_reduce: String = list.reduce { accu, element -> accu + "/" + element }
   println(result_list_reduce)

   var result_list_fold: String = list.fold(initial = "동물 친구들:", operation = { accu, element -> accu + " " + element })
   println(result_list_fold)
   println(result_list_fold == list.fold("동물 친구들:") { accu, element -> accu + " " + element })

   //////////////////////////
   // groupBy and groupingBy
   var map_groupBy_K = list.groupBy(keySelector = { it })
   println(map_groupBy_K)

   var map_groupBy_K_V = list.groupBy(keySelector = { it }, valueTransform = { it })
   println(map_groupBy_K_V)
   println(map_groupBy_K == map_groupBy_K_V)

   var map_groupBy_K_V_1 = list.groupBy({ it }, { it.length })
   println(map_groupBy_K_V_1)

   /////////////////////
   // groupingBy(Grouping<T, K> 객체)에 적용할 수 있는 aggregate, reduce, fold
   var map_groupingBy = list.groupingBy(keySelector = { it })
   println(map_groupingBy)

   var count_dup: Int = 0
   var map_groupingBy_aggregate =
       map_groupingBy.aggregate { key, accu: StringBuilder?, element, first ->
           ( if ( null === accu ) StringBuilder() else accu.append("-") )?.append(element)
       }
   println(map_groupingBy_aggregate)

   var map_groupingBy_reduce =
       map_groupingBy.reduce { key, accu, element ->
           accu + "-" + element
       }
   println(map_groupingBy_reduce)

   var map_groupingBy_fold =
       map_groupingBy.fold(
           initialValueSelector = { key: String, element: String -> key + " 문자열: " },
           operation            = { key: String, accu: String, element: String -> accu + element }
       )
   println(map_groupingBy_fold)
   println(map_groupingBy_fold ==
           map_groupingBy.fold(
               { key: String, element: String -> key + " 문자열: " },
               { key: String, accu: String, element: String -> accu + element }
           )
   )
}



+ Recent posts