Axiom Справочник языка

10 · Массивы

Словарь DICTIONARY

Словарь хранит записи, в которых ключ однозначно определяет значение. В отличие от массива, где элемент выбирается номером, здесь элемент выбирается ключом: названием товара, номером ящика или другим значением ключевого типа. Страница описывает тип DICTIONARY, обращение по ключу и его члены: Add, Exists, Remove, Keys, TryGetValue и Count.

Объявление типа

Тип словаря записывается словом DICTIONARY с двумя аргументами в круглых скобках. Первым указывается тип ключа, вторым — тип значения:

DIM <имя> AS DICTIONARY(<тип ключа>, <тип значения>)

Типы едины для всех записей одного словаря: неоднородный словарь не создаётся. Объявление сразу создаёт пустой готовый словарь, поэтому заполнять его можно с первой строки, без REDIM и без выделения элементов.

DIM prices AS DICTIONARY(STRING, INTEGER)
PRINT prices.Count

Программа выводит 0: словарь объявлен, но ещё не содержит записей.

Ключом может быть STRING, INTEGER или LONG. Числовые ключи разных типов не смешиваются: словарь с ключом INTEGER и словарь с ключом LONG — разные типы. Значением может быть число, строка, BOOLEAN, временное значение, запись TYPE или массив. Файлы, изображения, спрайты, тайлсеты, тайловые карты и функциональные значения в словаре не хранятся.

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

SUB ShowCount(prices AS DICTIONARY(STRING, INTEGER))
    PRINT prices.Count
END SUB

Зарезервированное слово

Слово DICTIONARY зарезервировано. Оно обозначает тип и не может быть именем, задаваемым программистом: переменной, константы, параметра, процедуры, функции, пользовательского типа, поля или метода. Запрет регистронезависим: Dictionary, dictionary и смешанная запись равнозначны DICTIONARY.

DIM Dictionary AS INTEGER

Эта программа отклоняется при разборе с MB1002: «Ключевое слово 'Dictionary' нельзя использовать как имя». То же сообщение выдаётся для поля, метода и имени после точки. Имена, содержащие слово целиком внутри, остаются допустимыми: dict, myDictionary и dictionaryCount не зарезервированы.

Синтаксис типа сохраняется: AS DICTIONARY(STRING, INTEGER) пишется с двумя аргументами. Запись без аргументов, например DIM x AS Dictionary, сообщает MB1001 и напоминает полную форму.

Имя типа в объявлении, параметре, поле и результате функции записывается одинаково. Полные правила имён приведены в статье Имена и области видимости.

Ключи и сравнение

Строковые ключи сравниваются точно, с учётом регистра, без удаления пробелов и без нормализации. Записи "яблоко", "Яблоко" и " яблоко" — три разных ключа, а не один.

DIM exact AS DICTIONARY(STRING, INTEGER)
exact.Add("яблоко", 1)
exact.Add("Яблоко", 2)
exact.Add(" яблоко", 3)
PRINT exact.Count

Программа выводит 3. Пустая строка тоже допустима как обычный ключ и отличается от отсутствующей записи:

DIM titles AS DICTIONARY(STRING, STRING)
titles.Add("", "без названия")
PRINT titles[""]

Программа выводит без названия.

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

Чтение и запись по ключу

Значение читается обращением по ключу в квадратных скобках:

<словарь>[<ключ>]

Результат имеет объявленный тип значения. Отсутствующий ключ сообщает ошибку времени выполнения MB3015 и не подменяется нулём или пустой строкой: отсутствие записи нельзя спутать с нулевой ценой.

DIM prices AS DICTIONARY(STRING, INTEGER)
prices.Add("яблоко", 30)
PRINT prices["яблоко"]
TRY
    PRINT prices["слива"]
CATCH problem
    PRINT problem.Code
END TRY

Программа выводит 30, затем MB3015.

Присваивание по ключу заменяет существующую запись либо добавляет отсутствующую:

<словарь>[<ключ>] = <значение>
DIM prices AS DICTIONARY(STRING, INTEGER)
prices["яблоко"] = 30
prices["яблоко"] = 35
PRINT prices["яблоко"]
PRINT prices.Count

Программа выводит 35 и 1: второе присваивание заменило значение, а не создало вторую запись.

Метод Add

<словарь>.Add(<ключ>, <значение>)

Add добавляет новую запись. Метод вызывается со скобками и не возвращает значения. Повторный ключ сообщает ошибку времени выполнения MB3016: вторая запись не создаётся и прежнее значение не меняется. Для замены используется присваивание по ключу.

DIM prices AS DICTIONARY(STRING, INTEGER)
prices.Add("яблоко", 30)
TRY
    prices.Add("яблоко", 40)
CATCH problem
    PRINT problem.Code
END TRY
PRINT prices["яблоко"]

Программа выводит MB3016, затем 30.

Метод Exists

<словарь>.Exists(<ключ>) AS BOOLEAN

Exists проверяет наличие ключа и возвращает TRUE или FALSE. Метод не изменяет словарь и не сообщает ошибку на отсутствующем ключе.

DIM prices AS DICTIONARY(STRING, INTEGER)
prices.Add("яблоко", 30)
PRINT prices.Exists("яблоко"), prices.Exists("слива")

Программа выводит TRUE FALSE.

Метод Remove

<словарь>.Remove(<ключ>) AS BOOLEAN

Remove удаляет запись и возвращает TRUE при успешном удалении. Если ключа нет, метод возвращает FALSE и не сообщает ошибку, поэтому повторное удаление безопасно.

DIM prices AS DICTIONARY(STRING, INTEGER)
prices.Add("груша", 45)
PRINT prices.Remove("груша")
PRINT prices.Remove("груша")
PRINT prices.Count

Программа выводит TRUE, FALSE и 0.

Свойство Count

<словарь>.Count AS INTEGER

Count — свойство, а не метод: оно записывается без скобок и возвращает число записей. Запись prices.Count() отклоняется при проверке программы: скобки отличают вызов метода от чтения свойства.

DIM prices AS DICTIONARY(STRING, INTEGER)
prices.Add("яблоко", 30)
prices.Add("груша", 45)
PRINT prices.Count

Программа выводит 2.

Метод Keys и порядок обхода

<словарь>.Keys() AS <тип ключа>[]

Keys() возвращает самостоятельный одномерный массив ключей в порядке добавления записей. Массив имеет границы 1 … Count; для пустого словаря действует каноническая пустая форма 1 TO 0. Метод вызывается со скобками.

DIM items AS DICTIONARY(STRING, INTEGER)
items.Add("а", 1)
items.Add("б", 2)
DIM keys[] AS STRING = items.Keys()
PRINT keys[1], keys[2]
PRINT LBOUND(keys[]), UBOUND(keys[])

Программа выводит а б, затем 1 2.

Порядок подчиняется трём правилам. Новая запись добавляется в конец. Замена значения по существующему ключу сохраняет место записи. Удаление и повторное добавление помещают ключ в конец.

DIM items AS DICTIONARY(STRING, INTEGER)
items.Add("а", 1)
items.Add("б", 2)
items.Add("в", 3)
items["б"] = 20
items.Remove("б")
items.Add("б", 200)
FOR EACH key IN items.Keys()
    PRINT key
NEXT key

Программа выводит а, в, б: замена сохранила место, а повторное добавление перенесло ключ в конец.

Полученный массив независим от состава словаря. Добавление, замена и удаление записей после вызова Keys() этот массив не меняют, поэтому начатый обход не получает внезапно новые ключи.

DIM items AS DICTIONARY(STRING, INTEGER)
items.Add("яблоко", 30)
DIM keys[] AS STRING = items.Keys()
items.Add("груша", 45)
PRINT items.Count
PRINT keys[1]

Программа выводит 2 и яблоко: словарь вырос, а снимок ключей остался прежним.

Прямой обход словаря не вводится: FOR EACH проходит по массиву, поэтому источником указывается items.Keys(). Правила обхода элемента описаны в статье FOR EACH.

Метод TryGetValue

<словарь>.TryGetValue(<ключ>, <переменная>) AS BOOLEAN

TryGetValue ищет ключ без исключения. Первый аргумент — ключ, второй — изменяемая переменная точного типа значения. При успехе переменная получает найденное значение, а метод возвращает TRUE. При отсутствии ключа переменная получает значение по умолчанию своего типа (например, 0 для INTEGER), метод возвращает FALSE, а запись не добавляется.

DIM prices AS DICTIONARY(STRING, INTEGER)
prices.Add("бесплатный", 0)
DIM price AS INTEGER
IF prices.TryGetValue("бесплатный", price) THEN
    PRINT "найдено: "; price
ELSE
    PRINT "нет товара"
END IF
IF prices.TryGetValue("слива", price) THEN
    PRINT "найдено: "; price
ELSE
    PRINT "нет товара, значение: "; price
END IF

Программа выводит найдено: 0, затем нет товара, значение: 0. Наличие определяется возвращённым BOOLEAN, поэтому найденная нулевая цена не смешивается с отсутствием товара.

Второй аргумент должен быть изменяемой переменной точного типа значения: временное значение и несовместимый тип отклоняются при проверке программы. Допустимы локальная переменная, параметр BYREF, глобальная переменная и переменная, захваченная вложенной подпрограммой; запись идёт в фактическую переменную вызывающего кода.

DIM prices AS DICTIONARY(STRING, INTEGER)
prices.Add("яблоко", 42)
DIM fromParameter AS INTEGER
ReadInto(prices, fromParameter)
PRINT fromParameter

SUB ReadInto(items AS DICTIONARY(STRING, INTEGER), BYREF result AS INTEGER)
    PRINT items.TryGetValue("яблоко", result)
END SUB

Программа выводит TRUE, затем 42: значение записано в переменную fromParameter через параметр BYREF.

Хранимые значения и присваивание словаря

Значения копируются при добавлении или замене: последующее изменение исходной переменной не меняет содержимое словаря. Массив и вложенные данные TYPE получают независимое хранилище. Чтение записи с сохранением в отдельную переменную также создаёт копию; непосредственное изменение выбранной записи словаря меняет сам словарь.

TYPE Item
    Price AS INTEGER
END TYPE
DIM items AS DICTIONARY(INTEGER, Item)
DIM apple AS Item
apple.Price = 30
items.Add(1, apple)
apple.Price = 99
PRINT items[1].Price

Программа выводит 30: запись скопирована при добавлении.

Обычное присваивание словаря создаёт независимую копию, включая вложенные данные. Порядок записей сохраняется. Изменение копии не меняет оригинал:

DIM prices AS DICTIONARY(STRING, INTEGER)
prices.Add("яблоко", 30)
DIM alias AS DICTIONARY(STRING, INTEGER)
alias = prices
alias["яблоко"] = 99
PRINT prices["яблоко"]

Программа выводит 30: изменение копии сохранило исходную цену.

Метод TYPE у значения словаря получает живой получатель. Изменения SELF записываются в саму запись словаря, а не во временную копию, и сохраняются даже если метод завершился ошибкой:

TYPE Item
    Price AS INTEGER

    SUB RaisePrice()
        SELF.Price = SELF.Price + 1
    END SUB
END TYPE

DIM prices AS DICTIONARY(STRING, Item)
DIM apple AS Item
apple.Price = 30
prices.Add("яблоко", apple)
prices["яблоко"].RaisePrice()
PRINT prices["яблоко"].Price
PRINT apple.Price

Программа выводит 31, затем 30: запись в словаре изменилась, а исходная переменная сохранила свою цену.

Словарь сохраняется в снимке состояния отладчика и восстанавливается при обратном шаге вместе с порядком записей. Если обратный шаг выполняется внутри метода записи, SELF сохраняет связь с записью словаря: повторное присваивание SELF заменяет саму запись. Активные обработчики TRY/CATCH/FINALLY также восстанавливаются, поэтому ошибка после повторного выполнения метода обрабатывается тем же CATCH. В отладочном отображении переменная словаря показывается как <DICTIONARY(STRING, INTEGER) записей: 2>.

Диагностика

Проверка программы сообщает об ошибках типа и формы обращения, а исполнение — об ошибках словаря.

  • MB2065 — тип ключа не STRING, INTEGER или LONG.
  • MB2066 — тип значения не поддерживается словарём: файл, графический ресурс или функциональное значение.
  • MB1002 — попытка использовать DICTIONARY как имя переменной, константы, параметра, процедуры, функции, типа, поля или метода.
  • MB1001 — тип DICTIONARY записан без двух аргументов.
  • MB2067 — неизвестный член словаря, скобки у свойства Count, отсутствие скобок у метода, пустые скобки у имени словаря и неверное число аргументов метода.
  • MB3015 — чтение отсутствующего ключа. Запись не подменяется значением по умолчанию.
  • MB3016 — повторный ключ в Add.

Для поиска без ошибки используется TryGetValue. Общие сведения о диагностике массивов и коллекций собраны в статье Ошибки массивов.

Запускаемые примеры

store_price_list.axm — ценник магазина: добавление, чтение, замена, удаление и обход. word_count.axm — подсчёт слов текста. keys_snapshot_order.axm — порядок ключей и независимый снимок. try_get_value.axm — поиск без исключения и нулевая цена. missing_key_error.axm — ошибка чтения отсутствующего ключа. duplicate_key_error.axm — ошибка повторного добавления. try_get_value_byref.axm — запись результата в параметр BYREF и глобальную переменную. type_method_receiver.axm — живой получатель метода TYPE. array_of_dictionaries.axm — массив словарей. reserved_name_error.axm — отказ использовать зарезервированное слово как имя. self_step_back.axm — замена всей записи через SELF и обработка ошибки после обратного шага.

См. также

Объявление DIM · Массивы · FOR EACH · Пользовательские типы · Присваивание