В Set два элемента имеют одинаковый хеш. Будет ли один считаться дубликатом другого?
Нет. Совпадение хешей само по себе не означает равенство элементов: Set дополнительно проверяет их эквивалентность через ==. Одинаковый хеш лишь означает, что элементы могут попасть в одну область поиска.
Хеширование появилось как способ ускорить поиск, вставку и удаление в больших наборах данных. Вместо последовательного сравнения элемента со всеми значениями структура сначала вычисляет хеш и быстро выбирает ограниченную область возможных совпадений.
Одного хеша недостаточно, потому что множество возможных элементов обычно значительно больше множества возможных хеш-значений. Поэтому коллизии — совпадения хешей у разных элементов — являются нормальной ситуацией, а не ошибкой.
Если считать элементы равными только при совпадении хешей, разные значения будут ошибочно удаляться как дубликаты. Это нарушит семантику Set: в нём может находиться только одно значение для каждой пары элементов, считающихся равными через ==.
Для пользовательского типа особенно опасна несогласованная реализация Hashable. Если два равных значения дают разные данные хешеру, поиск и операции множества могут работать некорректно; обратное условие — одинаковый хеш у неравных значений — допустимо.
При проверке наличия элемента Set сначала использует хеш, чтобы сузить поиск. Если находятся кандидаты с тем же хешем, структура сравнивает их с искомым значением через ==. Только успешное равенство означает, что элемент уже существует.
Контракт Hashable формулируется так: если a == b, то a и b обязаны подавать в Hasher одинаковые существенные данные. Но из одинаковых данных хеширования равенство не следует: хеш-функция может дать коллизию.
В примере элементы различаются по всем данным, участвующим в автоматически созданных == и hash(into:), поэтому оба находятся в множестве. Даже если бы их хеши случайно совпали, результат с точки зрения равенства не изменился бы.
Точный способ разрешения коллизий является деталью реализации стандартной библиотеки и не должен использоваться как публичный контракт. Разработчику важно соблюдать согласованность == и hash(into:), а не полагаться на уникальность хешей или конкретную внутреннюю структуру таблицы.
В приложении объект пользователя хранится в Set, а разработчик включает в hash(into:) только имя, хотя == сравнивает имя и идентификатор. Такой код может создавать много коллизий: разные пользователи с одинаковым именем будут попадать в одну область поиска, но при корректном == всё равно останутся разными элементами. Минус — потенциально менее эффективный поиск.
Другой вариант — включить в хеш идентификатор, но сравнивать пользователей только по имени. Это допустимо с точки зрения контракта, однако хеширование будет учитывать больше данных, чем логика равенства, что усложняет модель и может быть неожиданно для предметной области.
Выбранное решение — определить идентификатор как критерий равенства и хеширования, если именно он однозначно идентифицирует пользователя. Тогда семантика множества ясна, коллизии обычно редки, а изменение имени не влияет на положение пользователя в наборе.
1. Обязательно ли неравным элементам иметь разные хеши?
Нет. Обязательна только односторонняя гарантия: равные элементы должны хешироваться одинаково. Неравные элементы могут иметь одинаковый хеш, и структура обязана различать их дополнительной проверкой ==.
2. Что произойдёт, если значение, уже помещённое в Set, изменить?
Если изменить свойства, участвующие в равенстве или хешировании, элемент логически окажется не в той области, где Set ожидает его найти. После такой мутации поиск, удаление или проверка наличия могут вести себя неожиданно. Поэтому элементы множества должны оставаться неизменными с точки зрения данных, определяющих Hashable, пока находятся в наборе.
3. Можно ли использовать hashValue как постоянный идентификатор элемента?
Нет. hashValue предназначен для работы хешируемых коллекций, а не для хранения стабильных идентификаторов. Его значение может зависеть от запуска программы и деталей реализации; для идентификации следует использовать отдельное устойчивое поле, например UUID или доменный идентификатор.