Kotlin/Kotlin

Kotlin associateBy, groupBy 함수

Junuuu 2024. 3. 9. 00:01
728x90

개요

Kotlin의 Collection을 다루는 방법 중 associateBy 함수와 groupBy 함수에 대해서 알아보고자 합니다.

 

associateBy와 groupBy 함수

둘의 공통점은 collection의 특정한 키로 map을 만들어냅니다.

키는 KeySelector 매개변수에 정의되고 valueSelector는 키의 값에 어떤 내용을 저장할지를 정의합니다.

 

 

associateBy와 groupBy 함수의 차이점

associateBy 함수는 key가 동일할 때 마지막으로 등장한 값을 덮어씌워서 사용하며 groupBy는 key가 동일하더라도 list로 모든 값을 다룰 수 있습니다.

 

 

associateBy 내부구현

//key값만 받음
public inline fun <T, K> Iterable<T>.associateBy(keySelector: (T) -> K): Map<K, T> {
    val capacity = mapCapacity(collectionSizeOrDefault(10)).coerceAtLeast(16)
    return associateByTo(LinkedHashMap<K, T>(capacity), keySelector)
}

//key값과 value값을 같이 받음
public inline fun <T, K, V> Iterable<T>.associateBy(keySelector: (T) -> K, valueTransform: (T) -> V): Map<K, V> {
    val capacity = mapCapacity(collectionSizeOrDefault(10)).coerceAtLeast(16)
    return associateByTo(LinkedHashMap<K, V>(capacity), keySelector, valueTransform)
}

 

associateBy의 경우 Iterable 객체의 확장함수로 보입니다.

keySelector로 key의 인자를 받고 valueTransform으로 value에 값도 Optional으로 받을 수 있습니다.

이후에 반환값은 Map으로 반환을 수행합니다.

 

이후 map의 초기 capacity(용량, 사이즈)를 설정하고 associateByTo 메서드를 호출합니다.

//key값만 받음
public inline fun <T, K, M : MutableMap<in K, in T>> Iterable<T>.associateByTo(destination: M, keySelector: (T) -> K): M {
    for (element in this) {
        destination.put(keySelector(element), element)
    }
    return destination
}

//key값과 value값을 같이 받음
public inline fun <T, K, V, M : MutableMap<in K, in V>> Iterable<T>.associateByTo(destination: M, keySelector: (T) -> K, valueTransform: (T) -> V): M {
    for (element in this) {
        destination.put(keySelector(element), valueTransform(element))
    }
    return destination
}

인자로 받은 destination Map에다가 key값을 put 해주고 value로도 element값을 넣어주게 됩니다.

value값도 같이 받은경우라면 Map에 key, value값을 넣어주게 됩니다.

이런 과정때문에 자연스럽게 마지막인 key값으로 value를 덮어쓰게 됩니다.

 

associateBy 테스트

data class Person(val name: String, val city: String, val phone: String)

val people = listOf(                                           
	Person("John", "Boston", "+1-888-123456"),
	Person("Sarah", "Munich", "+49-777-789123"),
	Person("Svyatoslav", "Saint-Petersburg", "+7-999-456789"),
	Person("Vasilisa", "Saint-Petersburg", "+7-999-123456")
)


val result1 = people.associateBy { it.city }
println(result1)
val result2 = people.associateBy (Person::city, Person::phone)
println(result2)
val result3 = people.associateBy ({it.city}, {it.phone})
println(result3)

 

실제로 리스트에 associateBy 메서드를 사용하면 어떤 결과가 나올까요?

 

 

result1 결과

{Boston=Person(name=John, city=Boston, phone=+1-888-123456), Munich=Person(name=Sarah, city=Munich, phone=+49-777-789123), Saint-Petersburg=Person(name=Vasilisa, city=Saint-Petersburg, phone=+7-999-123456)}

result1 경우에는 valueSelector를 따로 지정해주지 않았기 때문에 city라는 key에 element가 그대로 적용된 결과가 나오게 됩니다.

 

result2, result3 결과

{Boston=+1-888-123456, Munich=+49-777-789123, Saint-Petersburg=+7-999-123456}

 

"Saint-Petersburg"라는 city가 중복되기 때문에 마지막 값인 +7-999-123456 전화번호로 값이 덮어씌워졌으며 list가 4개에서 3개의 map으로 줄어들었습니다.

 

groupBy 내부구현

public inline fun <T, K, V> Iterable<T>.groupBy(keySelector: (T) -> K, valueTransform: (T) -> V): Map<K, List<V>> {
    return groupByTo(LinkedHashMap<K, MutableList<V>>(), keySelector, valueTransform)
}

public inline fun <T, K, V, M : MutableMap<in K, MutableList<V>>> Iterable<T>.groupByTo(destination: M, keySelector: (T) -> K, valueTransform: (T) -> V): M {
    for (element in this) {
        val key = keySelector(element)
        val list = destination.getOrPut(key) { ArrayList<V>() }
        list.add(valueTransform(element))
    }
    return destination
}

groupBy도 동일하게 Iterable의 확장함수이며 groupByTo 메서드를 호출합니다.

groupByTo 메서드의 경우에는 값이 key를 기준으로 값을 가져오거나 넣어주고 리스트에 해당 값에 또 element를 추가합니다.

 

groupBy 테스트

val peopleCities = people.groupBy(Person::city, Person::name)

위와 동일하게 city라는 이름이 중복되는 상황에서 groupBy를 활용하면 결과가 어떻게 될까요?

 

{Boston=[John], Munich=[Sarah], Saint-Petersburg=[Svyatoslav, Vasilisa]}

Person의 이름의 덮어씌워지지 않고 List로 여러개의 값을 가지는 것을 볼 수 있습니다.

 

 

Playground에서 실습해보기

위의 예제를 Kotlin Playground에서 실습해 볼 수 있습니다.

 

 

 

  

 

 

 

참고자료

https://play.kotlinlang.org/byExample/05_Collections/10_associateBy