Что такое коллизия в контексте хеширования в Swift?

Ответ

Коллизия хешей — это ситуация, когда два разных объекта (с разными значениями свойств) возвращают одинаковое хеш-значение при вызове hashValue или hash(into:).

Почему это проблема? Коллизии влияют на производительность коллекций, основанных на хеш-таблицах (Dictionary, Set), но не нарушают их корректность, так как Swift использует дополнительное сравнение через оператор ==.

Пример коллизии:

struct Book: Hashable {
    let id: Int // Уникальный идентификатор
    let title: String
    // Упрощенная, плохая реализация хеша
    func hash(into hasher: inout Hasher) {
        hasher.combine(id % 2) // Хеш зависит только от четности id!
    }
    static func == (lhs: Book, rhs: Book) -> Bool {
        return lhs.id == rhs.id // Правильное сравнение
    }
}

let book1 = Book(id: 1, title: "Book A") // id=1 (нечетное) -> хеш X
let book2 = Book(id: 3, title: "Book B") // id=3 (нечетное) -> хеш X
// Коллизия! Разные книги, одинаковый хеш.

var library: Set<Book> = [book1, book2]
print(library.count) // 2. Коллизия обработана, оба объекта добавлены,
                     // но поиск будет медленнее из-за цепочек в хеш-таблице.

Как избегать коллизий и писать хороший Hashable:

  1. Комбинируйте все значимые для равенства свойства в методе hash(into:).
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)    // Уникальный id
        hasher.combine(title) // + название для полноты
    }
  2. Согласованность с ==: Два объекта, которые равны (== возвращает true), обязаны иметь одинаковый хеш. Обратное не обязательно (коллизии разрешены).
  3. Используйте Hasher: Не вычисляйте хеш вручную. Позвольте Swift-стандартной библиотеке сделать это эффективно, передавая свойства в hasher.combine(_:).

Итог: Коллизии ухудшают производительность коллекций (время поиска может деградировать до O(n)), но корректность работы гарантируется оператором ==.

Ответ 18+ 🔞

Ну и хуйня, конечно. Смотри, коллизия хешей — это когда два разных объекта, с разными данными, выдают один и тот же хеш, как два разных ключа отпирают одну и ту же дверь, блядь.

А в чём, собственно, пиздец? Коллизии бьют по скорости коллекций, которые на хеш-таблицах работают (Dictionary, Set), но не ломают их, потому что Swift, хитрая жопа, после хеша ещё и оператор == дергает, чтобы убедиться.

Вот тебе наглядный пиздец:

struct Book: Hashable {
    let id: Int // Уникальный, казалось бы
    let title: String
    // А вот реализация хеша — просто манда с ушами
    func hash(into hasher: inout Hasher) {
        hasher.combine(id % 2) // Хеш считаем только по чётности id! Гениально, блядь!
    }
    static func == (lhs: Book, rhs: Book) -> Bool {
        return lhs.id == rhs.id // А вот сравнение правильное
    }
}

let book1 = Book(id: 1, title: "Book A") // id=1 (нечёт) -> хеш X
let book2 = Book(id: 3, title: "Book B") // id=3 (нечёт) -> хеш X
// Коллизия нарисовалась! Книги разные, а хеш один, ебать.

var library: Set<Book> = [book1, book2]
print(library.count) // 2. Коллизию обработали, оба объекта влезли,
                     // но искать теперь будет медленнее, потому что в таблице цепочки образуются.

Как не быть мудаком и писать нормальный Hashable:

  1. Запихивай в хеш ВСЁ, что важно для равенства. Не выёбывайся.
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)    // Уникальный id
        hasher.combine(title) // И название за компанию, нахуй
    }
  2. Согласованность с == — святое: Если объекты равны (== говорит true), хеш у них обязан совпадать. Обратное — не обязательно, коллизии жить могут, но это пиздец для производительности.
  3. Доверяй Hasher: Не изобретай велосипед, не пытайся хеш вручную вычислять. Просто пихай свойства в hasher.combine(_:), и пусть стандартная библиотека сама разбирается, у неё опыта овердохуища.

Суть в чём: Коллизии превращают быстрый поиск (почти O(1)) в долбёжку по списку (O(n)), но программа от этого не сломается, потому что == всех выручает. Но ты же не хочешь, чтобы твой код работал как черепаха в сиропе? Вот и не косячь с хешами.