Как словарь использует Equatable протокол в Swift?
для того, чтобы решить этот вопрос, Я играл с пользовательской структурой, которая реализует протокол хэширования. Я пытаюсь увидеть, сколько раз перегрузка оператора эквивалентности (==
) вызывается в зависимости от того, есть ли хэш-столкновение или нет при заполнении Dictionary
.
обновление
@matt написал гораздо более чистый пример пользовательской структуры, которая реализует протокол Hashable и показывает как часто!--4--> и ==
вам звонил. Я копирую код ниже. Чтобы увидеть мой оригинальный пример, проверьте редактировать история.
struct S : Hashable {
static func ==(lhs:S,rhs:S) -> Bool {
print("called == for", lhs.id, rhs.id)
return lhs.id == rhs.id
}
let id : Int
var hashValue : Int {
print("called hashValue for", self.id)
return self.id
}
init(_ id:Int) {self.id = id}
}
var s = Set<S>()
for i in 1...5 {
print("inserting", i)
s.insert(S(i))
}
это дает результаты:
/*
inserting 1
called hashValue for 1
inserting 2
called hashValue for 2
called == for 1 2
called hashValue for 1
called hashValue for 2
inserting 3
called hashValue for 3
inserting 4
called hashValue for 4
called == for 3 4
called == for 1 4
called hashValue for 2
called hashValue for 3
called hashValue for 1
called hashValue for 4
called == for 3 4
called == for 1 4
inserting 5
called hashValue for 5
*/
поскольку Hashable использует Equatable для дифференциации хэш-коллизий (я предполагаю, в любом случае), я ожидал бы func ==()
только для вызова, когда есть хэш-коллизии. Однако в приведенном выше примере @matt нет никаких хэш-коллизий, и все же ==
is еще называют. В моих других экспериментах, заставляющих хэш-коллизии (см. историю редактирования этого вопроса),==
казалось, называлось случайным числом раз.
что здесь происходит?
2 ответов
Ну, вот ваш ответ:
что на самом деле происходит:
- мы хэш-значение после вставки.
- мы не используем хэши для сравнения элементов, только ==. Использование хэшей для сравнения разумно только в том случае, если вы храните хэши, но это означает большее использование памяти для каждого словаря. Компромисс, который оценка потребностей.
- мы пытаемся вставить элемент перед оценкой, может ли словарь соответствовать этому элементу. Это потому, что элемент может быть уже в Словарь, в таком случае нам не нужны дополнительные возможности.
- когда мы изменяем размер словаря, мы должны перефразировать все, потому что мы не сохраняли хэши.
Итак, что вы видите:
- один хэш-ключа поиска
- some == ' s (поиск пробела)
- хэши каждого элемента в коллекции (размер)
- один хэш ключа поиска (на самом деле совершенно расточительно, но не большое дело, учитывая, что это происходит только после перераспределения O)
- some == ' s (поиск пробела в новом буфере)
мы все ошибались. Они вообще не используют хэши. - только ==
- чтобы решить, является ли это отдельным ключом. А потом идет второй раунд звонков в ситуации, когда сбор выращивается.
я копирую свой ответ из bugs.swift.org здесь. Он говорит о наборах, но детали применяются к словарям таким же образом.
в хэшированных коллекциях столкновения могут возникать всякий раз, когда количество ведер меньше, чем пространство ключей. Когда вы создаете новый набор без указания минимальной емкости, набор может иметь только одно ведро, поэтому при вставке второго элемента происходит столкновение. Метод insert затем решит, если хранение должно быть выращено, используя что-то, называемое фактором нагрузки. Если хранилище было выращено, существующие элементы должны быть перенесены в новый буфер хранения. Вот когда вы видите все эти дополнительные вызовы hashValue при вставке 4.
причина, по которой вы все еще видите больше вызовов==, чем можно было бы ожидать, если количество ведер равно или выше количества элементов, связана с детализацией реализации расчета индекса ведра. Биты hashValue смешиваются или" перетасовываются " перед операцией по модулю. Это должно сократить чрезмерные столкновения для типов с плохими алгоритмами хэша.