Map: специфичные операции

В Map типы ключей и значений определяются пользователем. При этом благодаря доступу к значениям по их ключу открываются различные возможности для обработки записей: от получения значения по ключу, до фильтрации ключей и значений по отдельности. В этом разделе находится описание функций из стандартной библиотеки для обработки ассоциативных списков.

Получение ключей и значений

Для того, чтобы получить значение из Map, вы должны передать его ключ в качестве аргумента функции get(). Также поддерживается сокращённый синтаксис - [key]. Если такой ключ не найден, то вернётся null. Помимо этого существует функция getValue(), которая в случае отсутствия ключа бросит исключение. Также есть еще две функции для решения проблемы отсутствия ключа:

  • getOrElse() - работает так же, как и для списков: если ключ не найден, то вернётся результат выполнения лямбды.
  • getOrDefault() - если ключ не найден, то вернёт заданное значение по умолчанию.
fun main() {
    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
    //numbersMap.getValue("six") // exception!
}

Если для выполнения операций вам требуются все ключи или все значения из Map, то воспользуйтесь свойствами keys и values. keys - это набор (Set) всех ключей, а values - коллекция всех значений.

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

Фильтрация

Вы можете фильтровать ассоциативный список с помощью функции filter() как и другие коллекции. Для этого передайте filter() предикат с Pair в качестве аргумента, что позволит использовать и ключ, и значение в предикате.

fun main() {
    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) // {key11=11}
}

Помимо этого существует две функции фильтрации, специфичные именно для ассоциативного списка: filterKeys() для фильтра по ключам и filterValues() для фильтра по значениям. Обе возвращают новый ассоциативный список, в котором все записи соответствуют заданному предикату. Предикат функции filterKeys() проверяет только ключи элементов, а предикат функции filterValues() проверяет только значения.

fun main() {
    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) // {key1=1, key11=11}
    println(filteredValuesMap) // {key1=1, key2=2, key3=3}
}

Операторы plus и minus

Из-за наличия доступа к элементам по их ключам операторы plus (+) и minus (-) работают с ассоциативными списками иначе, чем с другими коллекциями. Оператор plus возвращает Map, которая содержит в себе элементы обоих операндов: первым операндом должен быть Map, а вторым может быть Pair или другая Map. Если второй операнд содержит в себе записи с ключами, которые есть в первом операнде, то в результирующую Map попадут записи из второго операнда.

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

Оператор minus создаёт Map на основе первого операнда, исключая те записи, ключи которых есть во втором операнде. Таким образом, второй операнд может быть либо одним ключом, либо коллекцией ключей: списком, множеством и так далее.

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

Подробнее об использовании операторов plusAssign (+=) и minusAssign (-=) с изменяемыми ассоциативными списками см. ниже Операции записи.

Операции записи

Изменяемые ассоциативные списки предлагают специфичные для них операции записи. Такие операции позволяют изменять содержимое ассоциативного списка, используя доступ к значениям по ключу.

Все операции записи придерживаются следующих правил:

  • Значения можно обновлять. В свою очередь, ключи никогда не меняются: как только вы добавили запись, её ключ остаётся неизменным.
  • Для каждого ключа всегда есть одно значение, связанное с ним. Вы можете добавлять и удалять записи целиком.

Ниже описаны функции для операций записи из стандартной библиотеки, доступные для использования с изменяемыми ассоциативными списками.

Добавление и обновление записи

Для добавления в изменяемый ассоциативный список новой пары "ключ-значение" используйте функцию put(). Когда новая запись помещается в LinkedHashMap (реализация Map по умолчанию), она добавляется в неё таким образом, что при итерации Map она отображается последней. В отсортированных ассоциативных списках положение новых элементов определяется порядком их ключей.

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

Для добавления нескольких записей за раз, используйте функцию putAll(). В качестве аргумента она принимает Map или группу из Pair: Iterable, Sequence, или Array.

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

И put(), и putAll() перезаписывают значения, если указанные ключи уже существуют в Map. Поэтому вы можете использовать их, чтобы обновить значения записей.

fun main() {
    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"]}") // 11
    println(numbersMap) // {one=11, two=2}
}

Помимо этого, вы можете использовать упрощённый вариант для добавления записей в ассоциативный список. Есть два способа:

fun main() {
    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) // {one=1, two=2, three=3, four=4, five=5}
}

Если вызвать эти операторы с ключом, который уже существует в ассоциативном списке, то его значение будет перезаписано.

Удаление записей

Для удаления записи из изменяемой Map, используйте функцию remove(). При вызове remove(), вы можете передать ей либо только ключ, либо и ключ, и его значение. При этом, если вы передадите и ключ, и значение, то элемент с этим ключом будет удалён только в том случае, если его значение совпадает с переданным значением.

fun main() {
    val numbersMap = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
    numbersMap.remove("one")
    println(numbersMap) // {two=2, three=3}
    numbersMap.remove("three", 4) // ничего не удаляет
    println(numbersMap) // {two=2, three=3}
}

Также вы можете удалять записи из ассоциативного списка используя свойства keys или values: просто вызовите функцию remove() для них. При вызове remove() с values, будет удалена только первая запись с таким значением.

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

Оператор minusAssign (-=) для изменяемых Map тоже доступен.

fun main() {
    val numbersMap = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
    numbersMap -= "two"
    println(numbersMap) // {one=1, three=3}
    numbersMap -= "five" // ничего не удаляет
    println(numbersMap) // {one=1, three=3}
}