Соответствует протоколу Hashable?

Я пытаюсь создать словарь с ключом в виде структуры, которую я создал, и значением в виде массива Ints. Однако я продолжаю получать сообщение об ошибке:

Тип DateStruct не соответствует протоколу Hashable

Я почти уверен, что реализовал необходимые методы, но по какой-то причине они все еще не работают.

Вот моя структура с реализованными протоколами:

struct DateStruct {
    var year: Int
    var month: Int
    var day: Int

    var hashValue: Int {
        return (year+month+day).hashValue
    }

    static func == (lhs: DateStruct, rhs: DateStruct) -> Bool {
        return lhs.hashValue == rhs.hashValue
    }

    static func < (lhs: DateStruct, rhs: DateStruct) -> Bool {
        if (lhs.year < rhs.year) {
            return true
        } else if (lhs.year > rhs.year) {
            return false
        } else {
            if (lhs.month < rhs.month) {
                return true
            } else if (lhs.month > rhs.month) {
                return false
            } else {
                if (lhs.day < rhs.day) {
                    return true
                } else {
                    return false
                }
            }
        }
    }
}

Кто-нибудь может объяснить мне, почему я все еще получаю ошибку?


person MarksCode    schedule 22.02.2017    source источник


Ответы (6)


Вам не хватает декларации:

struct DateStruct: Hashable {

И ваша == функция неверна. Вам следует сравнить три свойства.

static func == (lhs: DateStruct, rhs: DateStruct) -> Bool {
    return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day
}

Два разных значения могут иметь одно и то же хеш-значение.

person rmaddy    schedule 22.02.2017
comment
Спасибо! Кстати, почему == не так? Разве две даты с одним и тем же днем, месяцем и годом не будут иметь одинаковое значение hashValue? - person MarksCode; 22.02.2017
comment
Конечно, у двух одинаковых дат будет один и тот же хеш. Но две даты с одинаковым хешем не обязательно равны. Две разные даты могут иметь один и тот же хеш. Хорошо. Но две даты с одинаковым хешем не обязательно должны быть равными. - person rmaddy; 22.02.2017
comment
Ну, я запутался, в любом случае моего return (year+month+day).hashValue недостаточно, так как год, месяц и день разных дат могут составлять одно и то же, например 2000, 1, 2 и 2000, 2, 1? - person MarksCode; 22.02.2017
comment
Ваша реализация hashValue в порядке. Было бы правильно просто вернуть 42, если хотите (но, пожалуйста, не делайте этого). Из Hashable docs: Хеш-значение, предоставляемое свойством hashValue типа, является целым числом, одинаковым для любых двух экземпляров, которые сравниваются одинаково. То есть для двух экземпляров a и b одного типа, если a == b, то a.hashValue == b.hashValue. Обратное неверно: два экземпляра с равными хеш-значениями не обязательно равны друг другу. Вот почему вам нужно исправить вашу == функцию. - person rmaddy; 22.02.2017
comment
Понятно, комбинация hashValue и == делает работу по сравнению. Спасибо! - person MarksCode; 22.02.2017
comment
Не совсем. == делает работу по сравнению. Вы можете проводить сравнение без хеширования. hashValue делает значения хешируемыми, что позволяет использовать их в качестве ключей словаря или хранить в какой-либо другой форме хеш-карты / таблицы. Обратите внимание, что Hashable расширяет Equatable. Это означает, что хеширование зависит от сравнения, но сравнение не зависит от хеширования. Прочтите документацию для этих двух протоколов для получения дополнительной информации. - person rmaddy; 22.02.2017
comment
О, вот почему, если я скажу dict[DateStruct1] = 1 и dict[DateStruct2] = 2, он не будет обращаться к одной и той же словарной статье, даже если DateStruct1 и DateStruct2 могут иметь одно и то же hashValue, он также будет сравнивать их с помощью ==. - person MarksCode; 22.02.2017

Если вы не хотите использовать hashValue, вы можете объединить хэш ваших значений с методом hash(into:).

Для получения дополнительной информации см. Ответ: https://stackoverflow.com/a/55118328/1261547

person Lilo    schedule 12.03.2019

Кстати

var hashValue: Int 

устарело (за исключением старых деревьев наследования NSObject).

    func hash(into hasher: inout Hasher)
    {
        hasher.combine(year);
        hasher.combine(month) 
    ...

это новый способ

person Anton Tropashko    schedule 04.06.2020
comment
В чем преимущество этого подхода перед, например, Принятый ответ @rmaddy от трех лет назад? При каких условиях кто-то может предпочесть ваше предложение? - person Jeremy Caney; 05.06.2020
comment
Это не вопрос выгоды: требуется безумный ответ, но его недостаточно для удовлетворения требований Hashable. После добавления == ошибка остается, но xcode с 11.3 по 11.5 НЕ предлагает вам создать заглушку протокола для хэша (в - person Anton Tropashko; 05.06.2020

Если у класса есть поля типа (другой класс), этот класс должен принять Hashable.

пример

struct Project : Hashable {
    var activities: [Activity]?

}

Здесь класс Activity также должен использовать Hashable.

person Hatim    schedule 01.03.2021

Вы не указали протокол Hashable при определении структуры:

struct DateStruct: Hashable { ...

Следующий код взят из вашего примера и работает на игровой площадке. Обратите внимание, что здесь был изменен оператор ==:

import Foundation

struct DateStruct: Hashable {
    var year: Int
    var month: Int
    var day: Int

    var hashValue: Int {
        return (year+month+day).hashValue
    }

    static func == (lhs: DateStruct, rhs: DateStruct) -> Bool {
        return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day
    }

    static func < (lhs: DateStruct, rhs: DateStruct) -> Bool {
        if (lhs.year < rhs.year) {
            return true
        } else if (lhs.year > rhs.year) {
            return false
        } else {
            if (lhs.month < rhs.month) {
                return true
            } else if (lhs.month > rhs.month) {
                return false
            } else {
                if (lhs.day < rhs.day) {
                    return true
                } else {
                    return false
                }
            }
        }
    }
}

var d0 = DateStruct(year: 2017, month: 2, day: 21)
var d1 = DateStruct(year: 2017, month: 2, day: 21)

var dates = [DateStruct:Int]()
dates[d0] = 23
dates[d1] = 49

print(dates)

print(d0 == d1) // true

d0.year = 2018

print(d0 == d1) // false
person nbloqs    schedule 22.02.2017

Для простых структур, где все его свойства уже Hashable (т.е. Int, String, ...), мы можем соответствовать Hashable, просто объявив его (см. https://developer.apple.com/documentation/swift/hashable)

Так что нет необходимости реализовывать ни hashValue (который, кстати, устарел), ни == (потому что Hashable соответствует Equatable).

И поскольку мы реализуем оператор <, было бы разумно соответствовать Comparable, чтобы мы могли сортировать (т.е. [dateStructA, dateStructB, ...].sorted()).

Так что я бы сделал это так:

struct DateStruct: Comparable & Hashable {
    let year: Int
    let month: Int
    let day: Int

    static func < (lhs: DateStruct, rhs: DateStruct) -> Bool {
        if lhs.year != rhs.year {
           return lhs.year < rhs.year
        } else if lhs.month != rhs.month {
           return lhs.month < rhs.month
        } else {
           return lhs.day < rhs.day
        }
    }
}
person FranMowinckel    schedule 30.08.2020