TL;DR
Моя пользовательская структура реализует Hashable Protocol. Однако, когда возникают хэш-коллизии при вставке ключей в Dictionary
, они не обрабатываются автоматически. Как решить эту проблему?
Фон
Ранее я задавал этот вопрос Как реализовать протокол Hashable в Swift для массива Int (настраиваемая строковая структура). Позже я добавил мой собственный ответ, который, казалось, работал.
Однако недавно я обнаружил тонкую проблему при столкновении hashValue
при использовании Dictionary
.
Самый простой пример
Я упростил код, насколько это возможно, в следующем примере.
Пользовательская структура
struct MyStructure: Hashable {
var id: Int
init(id: Int) {
self.id = id
}
var hashValue: Int {
get {
// contrived to produce a hashValue collision for id=1 and id=2
if id == 1 {
return 2
}
return id
}
}
}
func ==(lhs: MyStructure, rhs: MyStructure) -> Bool {
return lhs.hashValue == rhs.hashValue
}
Обратите внимание на глобальную функцию, чтобы перегрузить оператор равенства (==), чтобы соответствовать Equatable Protocol, который требуется Протокол Hashable.
Проблема с тонким словарем
Если я создаю Dictionary
с MyStructure
в качестве ключа
var dictionary = [MyStructure : String]()
let ok = MyStructure(id: 0) // hashValue = 0
let collision1 = MyStructure(id: 1) // hashValue = 2
let collision2 = MyStructure(id: 2) // hashValue = 2
dictionary[ok] = "some text"
dictionary[collision1] = "other text"
dictionary[collision2] = "more text"
print(dictionary) // [MyStructure(id: 2): more text, MyStructure(id: 0): some text]
print(dictionary.count) // 2
равные хэш-значения вызывают замену клавиши collision1
клавишей collision2
. Предупреждений нет. Если такое столкновение произошло только один раз в словаре со 100 ключами, то его можно было бы легко упустить. (Мне потребовалось довольно много времени, чтобы заметить эту проблему.)
Очевидная проблема со словарным литералом
Если я повторяю это со словарным литералом, проблема становится намного более очевидной, потому что возникает фатальная ошибка.
let ok = MyStructure(id: 0) // hashValue = 0
let collision1 = MyStructure(id: 1) // hashValue = 2
let collision2 = MyStructure(id: 2) // hashValue = 2
let dictionaryLiteral = [
ok : "some text",
collision1 : "other text",
collision2 : "more text"
]
// fatal error: Dictionary literal contains duplicate keys
Вопрос
У меня создалось впечатление, что не нужно, чтобы hashValue
всегда возвращал уникальное значение. Например, MattT Thompson говорит,
Одно из самых распространенных заблуждений относительно реализации пользовательского хэша функция происходит от... мышления, что значения хэша должны быть разными.
И уважаемый пользователь SO @Gaffa говорит, что одним из способов обработки хеш-коллизий является
Рассмотрим хеш-коды, которые не являются уникальными, и используйте сопоставитель равенства для фактические данные для определения уникальности.
На мой взгляд, вопрос Нужно ли возвращать уникальные значения для хеш-функций хешируемого хеш файла, которые были возвращены уникальными значениями? не получил адекватного ответа на момент написания этой статьи.
После прочтения вопроса Swift Dictionary
Как обрабатываются хэш-столкновения?, я предположил, что Swift автоматически обрабатывает хэш-столкновения с Dictionary
. Но, по-видимому, это неверно, если я использую собственный класс или структуру.
Этот комментарий заставляет меня думать, что ответ заключается в том, как реализуется Equatable-протокол, но я не уверен, как его изменить.
func ==(lhs: MyStructure, rhs: MyStructure) -> Bool {
return lhs.hashValue == rhs.hashValue
}
Вызывается ли эта функция для каждого поиска в словаре или только при наличии хеш-коллизии? (Обновление: см. этот вопрос)
Что мне делать, чтобы определить уникальность, когда (и только когда) происходит столкновение хэшей?