Kotlin Map 다양한 사용법 (mapOf, keys, values, mapKeys, mapValues, toSortedMap, Comparator, getOrPut)

코틀린 Map에 대해서 다양한 사용 방법에 대해서 소개한다.

Map 개요

Kotlin의 Map(맵)은 Java의 Map을 기반으로 한 컬렉션이다. 키와 값의 쌍을 유지하며, 순서의 개념이 없다

Map & MutableMap

Map은 키와 값의 쌍으로 처리한다. Map은 읽기만 지원하는 인터페이스이다.

interface Map<K, out V>

MutableMap은 요소의 추가 및 삭제 처리를 제공한다.

interface MutableMap<K, V> : Map<K, V>

Map 생성 (mapOf)

불변(immutable) 맵은 Kotlin에 내장된 mapOf 함수로 생성할 수 있다.

val map: Map<String, Int> = mapOf("AAA" to 1, "BBB" to 2, "CCC" to 3)
println(map["AAA"]) //=> 1

// 루프 처리(forEach 메소드도 가능)
for ((k, v) in map) {
println("$k -> $v")
}

Map 생성

불변(immutable) 맵은 Kotlin에 내장된 mapOf 함수로 생성할 수 있다.

간단한 예로 읽기만 가능한 Map를 만들어 보도로 하겠다.

fun main() {
    var map = mapOf("Red" to "#f00", "Green" to "#0f0", "Blue" to "#00f")
    println(map["Red"])

    // 반복 처리(forEach 함수도 가능)
    for ((key, value) in map) {
        println("$key = $value")
    }
}

Output:

#f00
Red = #f00
Green = #0f0
Blue = #00f

Map 생성 후 요소를 추가/제거하고 싶다면 mutableMapOf 함수로 Map를 생성해야 한다.

val map: MutableMap<String, Int> = mutableMapOf("A" to 1, "B" to 2, "C" to 3)
map["A"] = 5
map["D"] = 10
for ((key, value) in map) {
    println("$key -> $value")
}

Output:

A -> 5
B -> 2
C -> 3
D -> 10

이 예제에서 알 수 있듯이, 요소의 변경과 추가는 모두 map[키] = 값이라는 표현으로 실행할 수 있다(Kotlin 내부에서 set(..) 함수 호출로 변환됨).

Map에서 키 목록, 값 목록 가져오기 (keys, values)

Map은 키-값 쌍을 갖은 콜렉션이지만, keys 속성으로 키 목록을 가져오거나, values 속성으로는 값만의 목록으로 가져올 수 있다.

val map = mapOf("A" to 1, "B" to 2, "C" to 3)
println(map.keys)
println(map.values)

Output:

[A, B, C]
[1, 2, 3]

Map의 키/값을 일괄 변경(mapKeys, mapValues)

Map 요소의 키를 일괄 변경(mapKeys)

기존 Map의 키로 사용되는 값을 일괄적으로 변경하려면 mapKeys를 사용한다. mapKeys에 람다식을 전달하면 각 요소의 키&값을 담고 있는 Map.Entry 객체가 해당 람다식에 순차적으로 전달된다. 각 루프 처리에서 람다식이 반환하는 반환값이 새로운 키로 처리된다. Map.Entry 객체에서는 key로 요소의 키를, value로 요소의 값을 참조할 수 있다.

예: Map의 모든 키를 대문자로 변환

val map = mapOf("a" to 1, "b" to 2, "c" to 3)
val updatedMap = map.mapKeys { it.key.uppercase() }
println(updatedMap)

Output:

{A=1, B=2, C=3}

mapKeys의 반환값은 Map 타입이다.

맵 요소의 값을 함께 변경(mapValues)

mapValues모두 mapKeys비슷하지만 맵 요소의 값을 함께 변경합니다. 람다 식은 변경 후 요소의 값이 반환 값이 되도록 구현합니다.

mapValuesmapKeys와 비슷하게 mapValues은 Map 요소의 값을 일괄적으로 변경한다. 람다식은 변경된 요소의 값이 반환값이 되도록 구현한다.

예: Map의 모든 값을 두 배로 만들기

val map = mapOf("a" to 1, "b" to 2, "c" to 3)
val updatedMap = map.mapValues { it.value * 2 }
println(updatedMap)

Output:

{a=2, b=4, c=6}

mapValues의 반환값은 Map 타입이다.

Map 정렬 및 반복 처리(toSortedMap)

Map을 키별로 정렬

Map의 요소를 출력할 때에 키순으로 정렬하여 출력해야 하는 경우가 있을 것이다. 이런 경우 toSortedMap을 사용하면 키로 정렬된 맵(SortedMap)으로 변환할 수 있다.

val map = mapOf("C" to 3, "A" to 1, "B" to 2)
for ((k, v) in map.toSortedMap()) {
    println("$k -> $v")
}

Output:

A -> 1
B -> 2
C -> 3

forEach 함수를 사용하여 루프를 작성하면, 처리 흐름이 왼쪽에서 오른쪽으로 일방향으로 진행되기 때문에 가독성이 다소 높아질 수 있다.

map.toSortedMap().forEach { k, v ->
    println("$k -> $v")
}

sortedMapOf라는 팩토리 함수를 사용하여 처음부터 SortedMap 타입으로 맵 인스턴스를 생성하는 방법도 있다.

val map = sortedMapOf("C" to 3, "A" to 1, "B" to 2)
map.forEach { k, v ->
    println("$k -> $v")
}

Map을 값으로 정렬하기

Map 요소의 값을 기준으로 정렬하고 해야 하는 경우도 있을 것이다. 몇 가지 방법을 알아보겠다.

키 & 값 목록으로 변환하여 값으로 정렬하는 방법

val map = mapOf("A" to 3, "B" to 1, "C" to 2)
val pairs = map.toList().sortedBy { it.second }
pairs.forEach { (k, v) -> println("$k, $v") }

Output:

B, 1
C, 2
A, 3

이 방법은 Map을 List<Pair<String, Int>>로 변환하여 값으로 정렬하여 출력하였다.

toSortedMap()에 사용자 지정 Comparator를 전달하는 방법

val map = mapOf("A" to 3, "B" to 1, "C" to 2)
val map2 = map.toSortedMap { k1, k2 ->
    map[k1]!! - map[k2]!!
}
map2.forEach { (k, v) -> println("$k, $v") }

Output:

B, 1
C, 2
A, 3

이 방법은 toSortedMap에 Comparator를 람다식으로 전달하여 정렬하고 출력하였다.

Map의 값을 처음 가져오려고 할 때에, 초기화(Map의 지연 초기화)(getOrPut)

MutableMapgetOrPut 함수를 사용하면 지정된 키에 해당하는 값을 찾을 수 없을 때, 해당 값을 람다식으로 초기화한 후 반환할 수 있다.

val map = mutableMapOf<String, Int>()
println(map["foo"])                // null (값이 존재하지 않음)
println(map.getOrPut("foo") { 0 }) // 0 (get과 동시에 초기값이 설정됨)
println(map["foo"])                // 0 (값이 설정되어 있음)

Output:

null
0
0

getOrPut() 함수를 사용하면 맵 값의 지연 초기화를 할 수 있다. 키&값 형식의 고정된 값을 얻고 싶지만, 각 값을 얻기 위해서는 다소 비용이 많이 드는 경우 캐시 용도로 사용할 수 있다(값이 변하지 않는다는 전제 하에).

class UserDb {
    // 사용자 나이 캐시
    private var userAge = mutableMapOf<String, Int>()

    // 사용자 나이를 가져온다(캐시를 이용).
    fun getAge(name: String): Int = userAge.getOrPut(name) { getAgeWithoutCache(name) }

    private fun getAgeWithoutCache(name: String): Int {
        // 초기값 계산에 시간이 걸린다고 가정
        return 10
    }
}

fun main() {
    val userDb = UserDb()
    val age: Int = userDb.getAge("devkuma")
    println(age)
}

Output:

10



최종 수정 : 2023-12-12