Swift: переопределение хэша NSObject без сбоя переполнения

Используя Swift 3, у меня есть некоторые NSObject подклассы, которые я перекрываю hash собственность и isEqual() функции для. (Я хочу, чтобы классы могли использоваться в качестве ключей в словаре, и я хочу, чтобы их массив мог быть отсортирован, но на самом деле не имеет значения, почему я переопределяю их.)

Возвращаясь к моим старым дням в C++/Java, я вспомнил, что "правильный" хэш включает простые числа и хэши свойств объекта. Эти вопросы говорят об этом стиле. Что-то вроде этого:

override public var hash: Int {
    var hash = 1
    hash = hash * 17 + label.hash
    hash = hash * 31 + number.hash
    hash = hash * 13 + (ext?.hash ?? 0)
    return hash
}

По крайней мере, так я и думал. Во время работы моего кода я увидел очень специфический сбой в моем hash переопределять:

EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

Посмотрев здесь на Stackru, я увидел множество этих сбоев, о которых спрашивали, и обычно ответом было то, что nil неявно разворачивается, вызывая сбой. Но в моем хэше нет опций. Поиграв в lldb, я понял, что проблема в переполнении целых чисел. Если вы сделаете это на детской площадке, вы увидите, что это вызывает ошибку:

`9485749857432985 * 39847239847239` // arithmetic operation '9485749857432985 * 39847239847239' (on type 'Int') results in an overflow

Ну, я делаю много сложений и умножений в своих переопределениях хэшей. (Трудно увидеть на детской площадке, но в lldb было очевидно, что переполнение стало причиной моего сбоя.) Читая о сбоях Swift из-за переполнения Int, я обнаружил, что вы можете использовать &* а также &+ предотвратить переполнение. Я не уверен, насколько хорошо работают хэши, но это не сработает, например:

override public var hash: Int {
    var hash = 1
    hash = hash &* 17 &+ label.hash
    hash = hash &* 31 &+ number.hash
    hash = hash &* 13 &+ (ext?.hash ?? 0)
    return hash
}

Вот мой вопрос: каков "правильный" способ написания такого рода hash переопределить, без возможности переполнения, и таким образом, который действительно обеспечивает хорошее хеширование?

Вот пример, который вы можете попробовать на игровой площадке. Я думаю, что это определенно приведет к EXC_BAD_INSTRUCTION для всех:

class DateClass: NSObject {
    let date1: Date
    let date2: Date
    let date3: Date

    init(date1: Date, date2: Date, date3: Date) {
        self.date1 = date1
        self.date2 = date2
        self.date3 = date3
    }

    override var hash: Int {
        var hash = 1
        hash = hash + 17 + date1.hashValue
        hash = hash + 31 + date2.hashValue
        hash = hash + 13 + date3.hashValue
        return hash
    }

    override public func isEqual(_ object: Any?) -> Bool {
        guard let rhs = object as? DateClass else {
            return false
        }
        let lhs = self

        return lhs.date1 == rhs.date1 &&
            lhs.date2 == rhs.date2 &&
            lhs.date3 == rhs.date3
    }
}

let dateA = Date()
let dateB = Date().addingTimeInterval(10)
let dateC = Date().addingTimeInterval(20)
let dateD = Date().addingTimeInterval(30)
let dateE = Date().addingTimeInterval(40)

let class1 = DateClass(date1: dateA, date2: dateB, date3: dateC)
let class2 = DateClass(date1: dateB, date2: dateC, date3: dateD)
let class3 = DateClass(date1: dateC, date2: dateD, date3: dateE)

var dict = [DateClass: String]()
dict[class1] = "one"
dict[class2] = "two"
dict[class3] = "three"

Бонусный вопрос: есть ли правильный способ справиться с hash значение, когда свойство вашего класса использует hashValue вместо? Я использовал их довольно взаимозаменяемо, но я не уверен, что это правильно.

1 ответ

hashValue (или же hash) действительно может быть что угодно. Пока два объекта, которые возвращаются true за isEqual также имеют такое же значение хеш-функции.

Вам не нужно беспокоиться о том, чтобы придумать какую-то магическую ценность, основанную на всех свойствах.

Ваш хеш может просто вернуть хеш одного свойства. Это позволит избежать любого переполнения. Или вы могли бы немного поработать со значениями хеша нескольких свойств. Некоторая комбинация "или", "и" и "xor".

Что касается вашего бонусного вопроса, нет проблем с вызовом hashValue на некоторых типах данных Swift при расчете результата NSObject hash метод. Оба возвращаются Int,

Другие вопросы по тегам