10 · Массивы
Множество HASHSET
Множество хранит набор уникальных значений одного объявленного типа и отвечает на вопрос, есть ли значение в наборе. В отличие от словаря, у элемента множества нет связанного значения: хранится только сам элемент, а повторное добавление не создаёт второй записи. Страница описывает тип HASHSET, его методы Add, Contains, Remove, ToArray, UnionWith, IntersectWith и свойство Count.
Объявление типа
Тип множества записывается словом HASHSET с одним аргументом в круглых скобках. Аргумент задаёт тип элемента и тем самым определяет, какие значения множество принимает:
DIM <имя> AS HASHSET(<тип элемента>)Объявление сразу создаёт пустое готовое множество. Отдельного выделения памяти и REDIM не требуется: элементами управляют методы множества с первой строки программы.
DIM found AS HASHSET(STRING)
PRINT found.Count
PRINT found.Add("ключ")
PRINT found.Add("ключ")
PRINT found.Count
PRINT found.Contains("ключ")
PRINT found.Remove("карта")
PRINT found.Remove("ключ")
PRINT found.CountПрограмма выводит 0, TRUE, FALSE, 1, TRUE, FALSE, TRUE и 0. Пустое множество сообщает нулевое количество и не содержит ни одного элемента. Первое добавление ключа возвращает TRUE, повторное — FALSE, но состав остаётся прежним. Удаление отсутствующей карты даёт FALSE, удаление имеющегося ключа — TRUE, после чего множество снова пусто.
Тип множества допустим в любом месте, где вообще допустим тип данных: в локальной и глобальной переменной, в параметре подпрограммы, в поле записи, в результате функции и в объявлении массива. Подробности размещения приведены ниже, в разделе Размещение типа в программе.
Зарезервированное слово
Слово HASHSET зарезервировано. Оно обозначает тип и не может быть именем, задаваемым программистом: переменной, константы, параметра, процедуры, функции, пользовательского типа, поля или метода. Запрет регистронезависим: HashSet, hashset и смешанная запись равнозначны HASHSET.
DIM HashSet AS INTEGER = 1Программа отклоняется при разборе с MB1002: «Ключевое слово 'HashSet' нельзя использовать как имя». То же сообщение выдаётся для параметра, поля, метода и имени после точки. Имена, содержащие слово внутри, остаются допустимыми: found, myHashSet и hashSetCount не зарезервированы, поскольку не совпадают со словом целиком.
Синтаксис типа требует ровно одного аргумента. Запись без аргументов, например DIM x AS HASHSET, сообщает MB1001: «Тип HASHSET записывается с одним аргументом: HASHSET(тип элемента)». Полные правила имён приведены в статье Имена и области видимости.
Типы элементов и совместимость
Элементом множества в текущей версии языка может быть значение типа STRING, INTEGER или LONG. Другие типы не принимаются: числа с дробной частью, BOOLEAN, временные значения, записи TYPE, массивы, файлы, графические ресурсы, функциональные значения и вложенные коллекции элементом не являются. Неподдержанный тип сообщает MB2068:
DIM bad AS HASHSET(BOOLEAN)Программа отклоняется при проверке с MB2068: «Тип элемента множества должен быть STRING, INTEGER или LONG, а указан BOOLEAN».
Тип элемента входит в идентичность коллекции. HASHSET(STRING), HASHSET(INTEGER) и HASHSET(LONG) — три разных типа, и совместимыми считаются только множества с полностью совпадающим типом элемента. Попытка объединить HASHSET(INTEGER) с HASHSET(LONG) отклоняется при проверке с MB2003: «Несовместимые типы: ожидался HASHSET(INTEGER), получен HASHSET(LONG). Тип коллекции должен совпадать точно, включая тип элемента».
Значение элемента приводится к объявленному типу по действующим правилам связывания ключей словаря; новых неявных преобразований множество не вводит. Передача числа в множество строк сообщает MB2003, передача лишнего аргумента в метод — MB2004.
Границы типа соблюдаются и в обе стороны. Множество не становится значением словаря: DICTIONARY(STRING, HASHSET(STRING)) отклоняется с MB2066, поскольку перечень значений словаря не расширяется. Множество не становится и элементом другого множества: HASHSET(HASHSET(STRING)) отклоняется с MB2068.
Сравнение элементов
Элементы сравниваются точно. Для строк это означает, что регистр, начальные и конечные пробелы и Unicode-представление значимы, а нормализация не выполняется. Записи "яблоко", "Яблоко", " яблоко" и "яблоко " — четыре разных элемента, а не один.
DIM words AS HASHSET(STRING)
words.Add("яблоко")
words.Add("Яблоко")
words.Add(" яблоко")
words.Add("")
PRINT words.Count
PRINT words.Contains("Яблоко")
PRINT words.Contains("яблоко ")Программа выводит 4, TRUE и FALSE. Четыре записи остались четырьмя разными элементами, а запись с завершающим пробелом отсутствует, потому что такого элемента не добавляли.
Пустая строка — обычный допустимый элемент и отличается от отсутствия элемента: она увеличивает Count и находится через Contains(""). Разные Unicode-представления одной и той же буквы тоже остаются разными элементами: составная форма и форма с отдельным комбинирующим знаком не сливаются.
DIM letters AS HASHSET(STRING)
letters.Add("й")
letters.Add("и" + CHR(774))
PRINT letters.CountПрограмма выводит 2. Код 774 — это комбинирующая краткая, которая зрительно соединяется с предыдущей буквой. Вторая запись содержит букву «и» и отдельный комбинирующий знак, поэтому она не совпадает с заранее составленной буквой «й», хотя обе записи выглядят одинаково.
Числовые элементы сравниваются по значению с учётом объявленного типа. Совпадение внутреннего хеша не делает значения одним элементом: 1 и 4294967296 в множестве LONG остаются двумя разными элементами.
Метод Add
<множество>.Add(<элемент>) AS BOOLEANAdd добавляет элемент и сообщает результат добавления. Метод вызывается со скобками и принимает ровно один аргумент объявленного типа элемента. Возвращается TRUE, если элемента раньше не было, и FALSE, если он уже присутствовал. Повторное добавление не является ошибкой, не создаёт второй записи и не меняет порядок элементов.
DIM found AS HASHSET(STRING)
PRINT found.Add("ключ")
PRINT found.Add("монета")
PRINT found.Add("ключ")
PRINT found.CountПрограмма выводит TRUE, TRUE, FALSE и 2. Третий вызов ничего не добавил, поэтому количество осталось равным двум.
Метод Add записывается со скобками. Запись found.Add без скобок отклоняется при проверке программы с MB2069, поскольку скобки отличают вызов метода от чтения свойства.
Метод Contains
<множество>.Contains(<элемент>) AS BOOLEANContains проверяет наличие элемента и возвращает TRUE или FALSE. Метод не изменяет множество, не добавляет отсутствующий элемент и не сообщает ошибку на отсутствующем значении, поэтому проверку можно выполнять многократно и в любом порядке.
DIM found AS HASHSET(STRING)
found.Add("ключ")
PRINT found.Contains("ключ")
PRINT found.Contains("карта")
PRINT found.CountПрограмма выводит TRUE, FALSE и 1. Проверка карты не добавила её в множество, поэтому количество осталось прежним.
Метод Remove
<множество>.Remove(<элемент>) AS BOOLEANRemove удаляет элемент и возвращает TRUE при успешном удалении. Если элемента нет, метод возвращает FALSE и не сообщает ошибку, поэтому повторное удаление безопасно. Удаление освобождает место, и добавленный позже элемент встаёт в конец, а не на прежнее место.
DIM found AS HASHSET(STRING)
found.Add("ключ")
PRINT found.Remove("ключ")
PRINT found.Remove("ключ")
PRINT found.CountПрограмма выводит TRUE, FALSE и 0.
Свойство Count
<множество>.Count AS INTEGERCount — свойство, а не метод: оно записывается без скобок и возвращает число элементов множества как INTEGER. Запись found.Count() отклоняется при проверке программы с MB2069 и напоминает правильную форму: «Count — свойство множества 'found' и записывается без скобок: found.Count».
DIM found AS HASHSET(STRING)
found.Add("ключ")
found.Add("монета")
PRINT found.CountПрограмма выводит 2.
Метод ToArray и порядок элементов
<множество>.ToArray() AS <тип элемента>[]ToArray() возвращает элементы множества как самостоятельный одномерный массив объявленного типа элемента. Метод вызывается со скобками и не принимает аргументов. Массив имеет нижнюю границу 1 и верхнюю границу Count; для пустого множества действует каноническая пустая форма 1 TO 0.
DIM found AS HASHSET(STRING)
DIM empty[] AS STRING = found.ToArray()
PRINT LBOUND(empty[]), UBOUND(empty[])
PRINT ARRAYLENGTH(empty[])Программа выводит 1 0 и 0. Верхняя граница меньше нижней, а длина равна нулю, поэтому обход пустого массива не выполняется ни разу.
Порядок элементов задаётся первым добавлением. Новый элемент встаёт в конец. Повторное добавление уже имеющегося элемента сохраняет его место. Удаление и последующее повторное добавление переносят элемент в конец.
DIM found AS HASHSET(STRING)
found.Add("а")
found.Add("б")
found.Add("в")
PRINT found.Add("б")
found.Remove("б")
found.Add("б")
FOR EACH item IN found.ToArray()
PRINT item
NEXT itemПрограмма выводит FALSE, затем а, в и б. Повторное добавление «б» не переставило элемент, а удаление с последующим добавлением перенесло его в конец.
Полученный массив независим от множества. Добавление, удаление и объединение после вызова ToArray() этот массив не меняют, поэтому начатый обход не получает внезапно новые элементы.
DIM found AS HASHSET(STRING)
found.Add("ключ")
found.Add("монета")
DIM saved[] AS STRING = found.ToArray()
found.Remove("ключ")
found.Add("карта")
PRINT saved[1], saved[2]
PRINT found.Count
FOR EACH item IN found.ToArray()
PRINT item
NEXT itemПрограмма выводит ключ монета, затем 2, затем монета и карта. Снимок сохранил прежний состав и порядок, а множество продолжило меняться независимо от него.
Прямой обход множества не вводится: FOR EACH по самому множеству отклоняется с MB2062, поэтому источником всегда указывается ToArray(). Правила обхода элемента описаны в статье FOR EACH.
Метод UnionWith
<множество>.UnionWith(<другое множество>)UnionWith — процедура: она изменяет получателя и не возвращает значения. Метод принимает множество точно того же типа, добавляет в получателя отсутствующие элементы аргумента и сохраняет порядок их обхода. Уже имеющиеся элементы не дублируются и не переставляются. Второй операнд не изменяется.
DIM first AS HASHSET(STRING)
first.Add("а")
first.Add("б")
DIM second AS HASHSET(STRING)
second.Add("б")
second.Add("в")
DIM combined AS HASHSET(STRING)
combined.UnionWith(first)
combined.UnionWith(second)
FOR EACH item IN combined.ToArray()
PRINT item
NEXT item
PRINT first.Count, second.CountПрограмма выводит а, б, в, затем 2 2. Получатель собрал объединение, а оба источника сохранили по два элемента.
Поскольку метод изменяет получателя, объединение без изменения источников выполняется через пустое множество: создаётся отдельный пустой получатель, и в него по очереди вызывается UnionWith для каждого источника. Именно так построен пример выше.
Операция с тем же экземпляром, в том числе через явный параметр BYREF, не изменяет ни состав, ни порядок. Объединение с пустым множеством тоже ничего не меняет. Метод не возвращает значение, поэтому его вызов записывается отдельным оператором; использование внутри PRINT отклоняется с MB2070.
Метод IntersectWith
<множество>.IntersectWith(<другое множество>)IntersectWith — процедура: она изменяет получателя и не возвращает значения. Метод принимает множество точно того же типа и оставляет в получателе только те элементы, которые есть в аргументе, сохраняя прежний порядок получателя. Второй операнд не изменяется.
DIM combined AS HASHSET(STRING)
combined.Add("а")
combined.Add("б")
combined.Add("в")
DIM second AS HASHSET(STRING)
second.Add("б")
second.Add("в")
combined.IntersectWith(second)
FOR EACH item IN combined.ToArray()
PRINT item
NEXT item
PRINT second.CountПрограмма выводит б, в, затем 2. Элемент «а» удалён как отсутствующий в аргументе, а порядок оставшихся сохранился.
Пустое множество в аргументе очищает получателя: общих элементов нет, поэтому Count становится равным нулю. Пересечение с самим собой, в том числе через явный параметр BYREF, сохраняет весь состав в прежнем порядке.
DIM found AS HASHSET(STRING)
found.Add("а")
found.Add("б")
found.UnionWith(found)
PRINT found.Count
DIM nothing AS HASHSET(STRING)
found.IntersectWith(nothing)
PRINT found.Count
DIM empty[] AS STRING = found.ToArray()
PRINT LBOUND(empty[]), UBOUND(empty[])Программа выводит 2, затем 0, затем 1 0. Самообъединение ничего не изменило, а пересечение с пустым множеством очистило получателя до канонического пустого состояния.
Независимые копии и объявления
Обычное присваивание создаёт независимую копию множества с теми же элементами в том же порядке. Изменение копии не меняет оригинал. Это правило действует также для параметра BYVAL и результата функции. BYREF явно разрешает изменение исходной переменной.
DIM found AS HASHSET(STRING)
found.Add("ключ")
DIM alias AS HASHSET(STRING)
alias = found
alias.Add("монета")
PRINT found.Count
PRINT found.Contains("монета")Программа выводит 1 и FALSE. Монета добавлена только в копию; исходное множество сохранило один ключ.
Два отдельных объявления также создают два разных пустых множества и не разделяют состояние:
DIM first AS HASHSET(STRING)
DIM second AS HASHSET(STRING)
first.Add("ключ")
PRINT first.Count, second.CountПрограмма выводит 1 0: заполнение первого множества не затронуло второе. Независимыми остаются и элементы массива множеств, и поля записей, объявленные отдельно. Общие правила присваивания собраны в статье Присваивание.
Размещение типа в программе
HASHSET — полноценный тип данных, а не только форма объявления. Он допустим в локальной и глобальной переменной, в параметре BYVAL и BYREF, в результате функции, в захваченной переменной вложенной подпрограммы, в поле записи и в элементе одномерного или многомерного массива.
TYPE Bag
found AS HASHSET(STRING)
END TYPE
DIM bag AS Bag
bag.found.Add("ключ")
PRINT bag.found.Count
DIM grid[1 TO 2] AS HASHSET(INTEGER)
grid[2].Add(7)
PRINT grid[2].Contains(7)
SUB Fill(items AS HASHSET(STRING), BYREF target AS HASHSET(STRING))
items.Add("монета")
target.Add("карта")
END SUB
DIM shared AS HASHSET(STRING)
shared.Add("ключ")
DIM second AS HASHSET(STRING)
Fill(shared, second)
PRINT shared.Count
PRINT second.CountПрограмма выводит 1, TRUE, 1 и 1. Поле записи хранит собственное множество. Элемент массива принимает значение своего типа, а второй элемент того же массива остаётся независимым и пустым. Параметр BYVAL получает копию и добавляет монету только в неё, поэтому shared сохраняет один элемент; параметр BYREF позволяет изменить и саму переменную вызывающего кода, поэтому second получает карту.
Захваченная переменная и локальная переменная подпрограммы подчиняются тем же правилам. Глобальное множество изменяется из вложенной подпрограммы, а локальное создаётся заново при каждом входе и не разделяется между вызовами.
DIM shared AS HASHSET(STRING)
Fill()
PRINT shared.Count
SUB Fill()
shared.Add("ключ")
END SUB
SUB ShowLocal()
DIM local AS HASHSET(STRING)
local.Add("монета")
PRINT local.Count
END SUB
ShowLocal()Программа выводит 1, затем 1: подпрограмма Fill изменила захваченное глобальное множество, а ShowLocal наполнила собственную локальную переменную.
Результат функции передаётся как обычное значение типа:
DIM made AS HASHSET(INTEGER) = MakeSet()
PRINT made.Count
FUNCTION MakeSet() AS HASHSET(INTEGER)
DIM result AS HASHSET(INTEGER)
result.Add(5)
RETURN result
END FUNCTIONПрограмма выводит 1.
Многомерный массив хранит множества так же, как одномерный. Каждый элемент получает отдельное пустое множество, а не общий шаблон:
DIM grid[1 TO 2, 1 TO 1] AS HASHSET(INTEGER)
grid[2, 1].Add(5)
PRINT grid[2, 1].Contains(5)
PRINT grid[1, 1].CountПрограмма выводит TRUE и 0: заполненный элемент содержит значение, а соседний остался пустым.
Предел числа элементов
Число элементов множества не превышает общего предела элементов коллекции RuntimeArrayValue.MaxElementCount. Предел защищает программу от необратимого отказа при бесконтрольном росте набора.
Если добавление уже имеющегося элемента выполняется на предельном размере, Add возвращает FALSE и не сообщает ошибку: состав не меняется, потому что новой записи не требуется. Если же требуется добавить новый элемент сверх предела, Add сообщает ресурсную ошибку MB3017, а состав получателя остаётся прежним. Для UnionWith вместимость проверяется до первого изменения, поэтому при логическом превышении предела получатель не остаётся частично объединённым.
Гарантия относится к предсказуемому логическому превышению предела. Исчерпание памяти процесса сверх этого отказа не обещает восстановления состояния и не перехватывается как обычная ошибка множества.
Диагностика
Проверка программы сообщает об ошибках типа и формы обращения, а исполнение — о ресурсном отказе.
MB1001— типHASHSETзаписан без аргумента типа элемента.MB1002— попытка использоватьHASHSETкак имя переменной, константы, параметра, процедуры, функции, пользовательского типа, поля или метода; запрет регистронезависим.MB2068— тип элемента неSTRING,INTEGERилиLONG, в том числе вложенное множество.MB2069— неверная форма обращения к множеству: индексированиеfound[<ключ>]иfound[], скобки у свойстваCount, отсутствие скобок у метода, неизвестный член множества.MB2070— вызовUnionWithилиIntersectWithвнутри выражения, где требуется значение, например вPRINT: эти методы ничего не возвращают.MB3017— ресурсный отказ при добавлении или объединении сверх предела числа элементов; состав получателя при этом не меняется.
К этим кодам примыкают общие диагностики типов: MB2003 сообщает о несовместимых типах коллекции или элемента, MB2004 — о неверном числе аргументов метода, MB2062 — о недопустимом источнике FOR EACH, MB2066 — о недопустимом значении словаря. Индексирование множества не является операцией множества: позиционного доступа и присваивания по индексу нет, элементами управляют только методы. Общие сведения об ошибках коллекций собраны в статье Ошибки массивов.
Запускаемые примеры
found_items.axm — найденные предметы: повторное добавление и проверка наличия. union_intersection.axm — объединение через пустого получателя и пересечение. snapshot_order.axm — независимый снимок и порядок элементов. shared_instance.axm — независимая копия при присваивании и отдельные объявления. self_operations.axm — операции с самим собой и с пустым аргументом. step_back.axm — пошаговая отладка и обратный шаг. unsupported_element_error.axm — отказ неподдержанного типа элемента. reserved_name_error.axm — отказ использовать зарезервированное слово как имя.
См. также
Словарь DICTIONARY · Массивы · Пользовательские типы · Имена и области видимости · Ошибки массивов · Объявление DIM · FOR EACH