TL; DR
Моя настраиваемая структура реализует протокол Hashable. . Однако, когда при вставке ключей в 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
не обязательно всегда возвращать уникальное значение. Например, Мэтт Томпсон говорит,
Одно из наиболее распространенных заблуждений о реализации пользовательской хеш-функции происходит от ... мысли, что хеш-значения должны быть разными.
А уважаемый пользователь SO @Gaffa говорит, что один из способов справиться с коллизиями хешей - это
Считайте хэш-коды неуникальными и используйте средство сравнения фактических данных для определения уникальности.
На мой взгляд, вопрос Делает ли быстрый хешируемый протокол хеш-функции должны возвращать уникальные значения? на момент написания этой статьи не было дано адекватного ответа.
Прочитав вопрос о Swift Dictionary
Как обрабатываются хеш-коллизии?, я предположил, что Swift автоматически обработал хеш-коллизии с Dictionary
. Но очевидно, что это не так, если я использую собственный класс или структуру.
Этот комментарий заставляет меня думаю, что ответ заключается в том, как реализован протокол Equatable, но я не уверен, как мне его изменить.
func ==(lhs: MyStructure, rhs: MyStructure) -> Bool {
return lhs.hashValue == rhs.hashValue
}
Эта функция вызывается при каждом поиске ключа словаря или только при конфликте хешей? (Обновление: см. этот вопрос)
Что мне делать, чтобы определить уникальность, когда (и только когда) возникает хеш-коллизия?
1
&2
для hashValues только для демонстрации столкновения. правильно? - person Honey   schedule 18.11.20162
и2
для hashValues является демонстрацией коллизии. (Идентификаторы:1
и2
.) @Honey - person Suragch   schedule 19.11.2016