Map 관련 연산

Map 관련 연산 (Map-specific operations)

map에서는 키와 값의 타입이 모두 사용자 정의됩니다. map 항목(entry)에 키 기반으로 접근하면, 키로 값을 얻는 것부터 키와 값을 각각 필터링하는 것까지 다양한 map 관련 처리 기능을 사용할 수 있어요. 이 페이지에서는 표준 라이브러리의 map 처리 함수들을 설명합니다.

출처: Kotlin 공식 문서 — Map-specific operations

본문

키와 값 가져오기 (Retrieve keys and values)

map에서 값을 가져오려면 get() 함수의 인자로 키를 제공해야 해요. 간단한 [key] 문법도 지원됩니다. 주어진 키를 찾을 수 없으면 null을 반환합니다. 약간 다른 동작을 하는 getValue() 함수도 있어요. 이 함수는 키를 map에서 찾을 수 없으면 예외를 던집니다. 추가로 키가 없을 때를 처리할 수 있는 옵션도 여러 가지가 있어요.

  • getOrElse()는 리스트에서와 같은 방식으로 동작합니다. 존재하지 않는 키의 값은 주어진 람다 함수에서 반환됩니다.
  • getOrDefault()는 키를 찾을 수 없을 때 지정된 기본값을 반환해요.

nullable 값을 가진 map에는 다음 함수들을 대신 사용할 수 있는데, 이들은 누락된 키와 null 값을 명시적으로 처리합니다.

  • getOrElseIfNull()은 키가 없거나 null 값을 가질 때 지정된 기본값의 결과를 반환합니다.
  • getOrElseIfMissing()은 키가 없을 때 지정된 기본값의 결과를 반환해요.

이 함수들 사이의 차이를 보여주는 예시를 볼게요.

@OptIn(ExperimentalStdlibApi::class)
fun main() {
//sampleStart
    val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
    println(numbersMap.get("one"))
    // 1

    println(numbersMap["one"])
    // 1

    println(numbersMap.getOrDefault("four", 10))
    // 10

    println(numbersMap["five"])
    // null
    
    val nullableMap = mapOf("one" to 1, "two" to null)
    println(nullableMap.getOrElseIfNull("two") { 0 })
    // 0

    println(nullableMap.getOrElseIfMissing("two") { 0 })
    // null

    // Throws an exception because "six" is missing from the map
    // numbersMap.getValue("six")

//sampleEnd
}

map의 모든 키나 모든 값에 연산을 수행하려면 keysvalues 속성에서 각각 가져올 수 있어요. keys는 모든 map 키의 집합(set)이고, values는 모든 map 값의 컬렉션입니다.

fun main() {
//sampleStart
    val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
    println(numbersMap.keys)
    println(numbersMap.values)
//sampleEnd
}

필터링 (Filter)

다른 컬렉션과 마찬가지로 filter() 함수로 map을 필터링할 수 있어요. map에 filter()를 호출할 때는 Pair를 인자로 받는 조건을 전달합니다. 이렇게 하면 필터링 조건에서 키와 값을 모두 사용할 수 있어요.

fun main() {
//sampleStart
    val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
    val filteredMap = numbersMap.filter { (key, value) -> key.endsWith("1") && value > 10}
    println(filteredMap)
//sampleEnd
}

map을 필터링하는 두 가지 특정 방법도 있어요. 키로 필터링하거나 값으로 필터링하는 것이죠. 각 방법에는 filterKeys()filterValues() 함수가 있습니다. 둘 다 주어진 조건과 일치하는 항목들의 새 map을 반환해요. filterKeys()의 조건은 요소 키만 확인하고, filterValues()의 조건은 값만 확인합니다.

fun main() {
//sampleStart
    val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
    val filteredKeysMap = numbersMap.filterKeys { it.endsWith("1") }
    val filteredValuesMap = numbersMap.filterValues { it < 10 }

    println(filteredKeysMap)
    println(filteredValuesMap)
//sampleEnd
}

Plus와 minus 연산자

요소에 키로 접근하기 때문에, plus(+)와 minus(-) 연산자는 map에서 다른 컬렉션과 다르게 동작해요. plus는 양쪽 피연산자의 요소를 담은 Map을 반환합니다. 왼쪽이 Map이고 오른쪽이 Pair 또는 다른 Map인 형태죠. 오른쪽 피연산자에 왼쪽 Map에 이미 있는 키의 항목이 포함되어 있다면, 결과 map에는 오른쪽의 항목이 담겨요.

fun main() {
//sampleStart
    val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
    println(numbersMap + Pair("four", 4))
    println(numbersMap + Pair("one", 10))
    println(numbersMap + mapOf("five" to 5, "one" to 11))
//sampleEnd
}

minus는 왼쪽 Map의 항목 중 오른쪽 피연산자의 키를 가진 것들을 제외하고 Map을 만듭니다. 그래서 오른쪽 피연산자는 단일 키이거나 키의 컬렉션(리스트, 셋 등)일 수 있어요.

fun main() {
//sampleStart
    val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
    println(numbersMap - "one")
    println(numbersMap - listOf("two", "four"))
//sampleEnd
}

변경 가능한 map에서 plusAssign(+=)와 minusAssign(-=) 연산자를 사용하는 방법은 아래 Map 쓰기 연산을 참고하세요.

Map 쓰기 연산 (Map write operations)

변경 가능한 map은 map 관련 쓰기 연산을 제공해요. 이 연산들은 값에 키 기반으로 접근해 map 내용을 바꿀 수 있게 해줍니다.

map의 쓰기 연산을 정의하는 특정 규칙이 있어요.

  • 값은 업데이트할 수 있습니다. 반면 키는 절대 변하지 않아요. 한 번 항목을 추가하면 그 키는 상수입니다.
  • 각 키에는 항상 하나의 값만 연결됩니다. 항목 전체를 추가하거나 제거할 수 있어요.

아래는 변경 가능한 map에서 사용할 수 있는 쓰기 연산용 표준 라이브러리 함수들의 설명입니다.

항목 추가 및 업데이트 (Add and update entries)

변경 가능한 map에 새 키-값 쌍을 추가하려면 put()을 사용해요. 새 항목이 (기본 map 구현인) LinkedHashMap에 넣어지면, map을 순회할 때 가장 마지막에 오도록 추가됩니다. 정렬된 map에서는 새 요소의 위치가 키의 순서로 결정됩니다.

fun main() {
//sampleStart
    val numbersMap = mutableMapOf("one" to 1, "two" to 2)
    numbersMap.put("three", 3)
    println(numbersMap)
//sampleEnd
}

여러 항목을 한 번에 추가하려면 putAll()을 사용하세요. 그 인자는 Map이거나 Pair들의 그룹(Iterable, Sequence, Array)일 수 있습니다.

fun main() {
//sampleStart
    val numbersMap = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
    numbersMap.putAll(setOf("four" to 4, "five" to 5))
    println(numbersMap)
//sampleEnd
}

put()putAll() 모두 주어진 키가 이미 map에 존재하면 그 값을 덮어씁니다. 그래서 이들을 map 항목의 값을 업데이트하는 데 쓸 수 있어요.

fun main() {
//sampleStart
    val numbersMap = mutableMapOf("one" to 1, "two" to 2)
    val previousValue = numbersMap.put("one", 11)
    println("value associated with 'one', before: $previousValue, after: ${numbersMap["one"]}")
    println(numbersMap)
//sampleEnd
}

map에 새 항목을 추가하는 데는 간단한 연산자 형태도 쓸 수 있어요. 두 가지 방법이 있습니다.

  • plusAssign(+=) 연산자.
  • set()의 별칭인 [] 연산자.
fun main() {
//sampleStart
    val numbersMap = mutableMapOf("one" to 1, "two" to 2)
    numbersMap["three"] = 3     // calls numbersMap.put("three", 3)
    numbersMap += mapOf("four" to 4, "five" to 5)
    println(numbersMap)
//sampleEnd
}

키가 map에 있을 때 호출되면, 이 연산자들은 해당 항목의 값을 덮어씁니다.

누락 항목의 기본값 추가

기존 값을 반환하거나 값이 없을 때 기본값을 추가하려면 .getOrPut() 확장 함수를 사용해요. 키가 없거나 null 값을 가지면, .getOrPut()은 기본값을 저장하고 그것을 반환합니다.

nullable 값을 가진 map에서는 .getOrPutIfNull().getOrPutIfMissing() 함수를 사용해서 null 값이 어떻게 처리될지 제어할 수 있어요.

  • getOrPutIfNull()getOrPut()처럼 동작하며, 키가 없거나 null 값일 때 기본값을 사용합니다.
  • getOrPutIfMissing()은 키가 없을 때만 기본값을 사용해요.

getOrPutIfNull()getOrPutIfMissing() 함수는 Experimental입니다. 이 기능을 사용하려면 @OptIn(ExperimentalStdlibApi::class) 애노테이션을 쓰세요.

예시를 볼게요.

@OptIn(ExperimentalStdlibApi::class)
fun main() {
//sampleStart
    val mapForNull = mutableMapOf<String, Int?>("one" to null)
    val mapForMissing = mutableMapOf<String, Int?>("one" to null)

    // Replaces the value if "one" has a null value
    mapForNull.getOrPutIfNull("one") { 1 }

    println(mapForNull)
    // {one=1}

    // Keeps the null value because "one" exists in the map
    mapForMissing.getOrPutIfMissing("one") { 1 }

    println(mapForMissing)
    // {one=null}
//sampleEnd
}

항목 제거 (Remove entries)

변경 가능한 map에서 항목을 제거하려면 remove() 함수를 사용해요. remove()를 호출할 때는 키 또는 전체 키-값 쌍을 전달할 수 있습니다. 키와 값을 모두 지정하면, 해당 키의 요소는 그 값이 두 번째 인자와 일치할 때만 제거됩니다.

fun main() {
//sampleStart
    val numbersMap = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
    numbersMap.remove("one")
    println(numbersMap)
    numbersMap.remove("three", 4)            //doesn't remove anything
    println(numbersMap)
//sampleEnd
}

변경 가능한 map에서 키나 값으로 항목을 제거할 수도 있어요. 이렇게 하려면 map의 키나 값에 remove()를 호출하면서 항목의 키나 값을 제공하면 됩니다. 값에 대해 호출하면 remove()는 주어진 값을 가진 첫 번째 항목만 제거해요.

fun main() {
//sampleStart
    val numbersMap = mutableMapOf("one" to 1, "two" to 2, "three" to 3, "threeAgain" to 3)
    numbersMap.keys.remove("one")
    println(numbersMap)
    numbersMap.values.remove(3)
    println(numbersMap)
//sampleEnd
}

변경 가능한 map에는 minusAssign(-=) 연산자도 사용할 수 있습니다.

fun main() {
//sampleStart
    val numbersMap = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
    numbersMap -= "two"
    println(numbersMap)
    numbersMap -= "five"             //doesn't remove anything
    println(numbersMap)
//sampleEnd
}

더 알아보기 (Learn more)