5. Основы Kotlin. Ассоциативные массивы Maps и множества Sets

Вернуться к предыдущему разделу

Введение

В прошлом уроке мы познакомились с вами с различными структурами данных, которые предназначены для хранения упорядоченной последовательности элементов. Понятно, что это далеко не единственный тип структур данных, которые существуют, и сегодня мы с вами познакомимся с двумя новыми структурами данных.

Чтобы понять первую структуру данных — ассоциативный массив — далеко ходить не надо, достаточно вспомнить о такой штуке как толковый словарь. Он связывает элементы отношением «ключ»-«значение»: для определенных слов (ключей) он содержит их описание (значения), для всех остальных — не содержит ничего. Подобной структурой обладают, на самом деле, многие вещи: набор товаров с их ценами, список контактов в телефоне, рестораны и рейтинги, и т.д. Основная операция, которую они поддерживают, — это достать значение, соответствующее интересующему нас ключу, т.е. то, что вы делаете, когда ищете значение неизвестного вам слова в словаре.

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

Что мы здесь видим? Наша функция принимает на вход список покупок: параметр shoppingList типа List<String> — и набор цен для товаров: параметр costs типа Map<String, Double>. Данный параметризованный тип Map<Key, Value> и является типом ассоциативного массива, у которого типовой параметр Key задает тип ключей, а Value — тип значений. В нашем случае набор товаров с ценами имеет тип Map<String, Double>, т.е. для названия товара содержит его цену в виде действительного числа.

Для того, чтобы считать общую стоимость выбранного набора товаров, мы заводим новую изменяемую переменную totalCost, которая изначально равна нулю и которую мы возвращаем как результат в конце функции при помощи return. После этого мы проходимся по списку покупок при помощи цикла for и для каждой покупки пытаемся достать ее цену из нашего ассоциативного массива при помощи операции индексирования. В отличии от индексирования для списка, операция индексирования map[key] для ассоциативного массива пытается достать элемент не по какому-то целочисленному индексу, а по ключу соответствующего типа — в нашем случае, по названию товара, т.е. строке.

А вот дальше мы знакомимся с такой очень интересной вещью как null. Как мы отметили раньше, ассоциативный массив содержит пары «ключ»-«значение», однако для некоторых ключей соответствующего им значения может не быть. Вместе с тем, просто так вернуть «ничего» мы не можем. Как раз для таких ситуаций и необходим объект null — операция индексирования для ассоциативного массива возвращает null в случае, если для заданного ключа нет значения. После того, как мы проверили, что для товара есть его стоимость (itemCost != null), мы добавляем ее к общей стоимости набора; в противном случае мы считаем, что данная покупка просто игнорируется.

Попробуем написать тесты для нашей функции.

Как видно из тестов, для создания ассоциативного массива может использоваться функция mapOf(), которая принимает на вход набор пар «ключ»-«значение» типа Pair<A, B> (в нашем случае, Pair<String, Double>). Для создания пары можно использовать либо конструкцию Pair(a, b), либо запись a to b, обе из которых создадут пару из a и b. Для того, чтобы обратиться к первому или второму элементу пары pair, следует использовать запись pair.first или pair.second соответственно.

В нашем случае мы создаем пары из названия товара и его стоимости (хлеб за 50.0 и молоко за 100.0), после чего собираем из них ассоциативный массив. Затем мы проверяем три случая:

  • Список покупок содержит хлеб и молоко, и общая стоимость должна быть равна 150.0
  • Список покупок, кроме хлеба и молока, содержит еще кефир, но — так как его стоимости мы не знаем — мы его игнорируем, и общая стоимость все равно должна быть равна 150.0
  • Для какого-то списка покупок с пустым ассоциативным массивом (мы не знаем ни одной цены товара) общая стоимость должна быть равна 0.0

В третьем случае мы создаем пустой ассоциативный массив при помощи функции mapOf() без аргументов. Типовые параметры в данном случае компилятор Котлина понимает из того, какой тип должен быть у второго аргумента функции shoppingListCost, поэтому их можно не указывать.

Распространённые операции над ассоциативными массивами

Рассмотрим основные операции, доступные над ассоциативными массивами.

  • map[key] / map.get(key) возвращает значение для ключа key или null в случае, если значения нет
  • map.size / map.count() возвращает количество пар «ключ»-«значение» в ассоциативном массиве
  • map + pair возвращает новый ассоциативный массив на основе map, в который добавлено (или изменено) значение ключа, соответствующее паре «ключ»-«значение» из pair
  • map - key возвращает новый ассоциативный массив на основе map, из которого, наоборот, удалено значение ключа key
  • map1 + map2 собирает два ассоциативных массива в один, причем пары «ключ»-«значение» из map2 вытесняют значения из map1
  • map - listOfKeys возвращает новый ассоциативный массив на основе map, в котором нет ключей из списка listOfKeys
  • map.getOrDefault(key, defaultValue) является расширенной версией операции индексирования. В случае, если в map есть значение для ключа key, данное выражение вернет его; если значения нет, то будет возвращено значение по умолчанию defaultValue.
  • key in map / map.contains(key) / map.containsKey(key) возвращает true, если map содержит значение для ключа key и false в противном случае
  • map.containsValue(value) возвращает true, если map содержит значение value для хотя бы одного ключа и false в противном случае

Добавить комментарий