符合Hashable协议?

37
我试图创建一个字典,其中键是我创建的结构体,值是整数数组。但是,我一直收到以下错误信息:
“类型'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
                }
            }
        }
    }
}

有人可以解释一下为什么我仍然会收到这个错误吗?

7个回答

41

你缺少了声明:

struct DateStruct: Hashable {

而你的==函数是错误的。你应该比较这三个属性。

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

两个不同的值具有相同的哈希值是可能的。


1
谢谢!顺便问一下,为什么使用==是错误的?两个日期如果具有相同的日、月和年,它们的哈希值不是应该相同吗? - MarksCode
当然,两个相等的日期将具有相同的哈希值。但是,具有相同哈希值的两个日期不一定相等。两个不同的日期可以具有相同的哈希值。这没问题。但是,具有相同哈希值的两个日期不一定相等。 - rmaddy
你的 hashValue 实现没问题。如果需要,你可以简单地返回 42(但请不要这样做)。从 Hashable 文档中可以了解到:“一个哈希值由类型的 hashValue 属性提供,它是一个整数,对于任意两个相等的实例,它是相同的。也就是说,对于同一类型的两个实例 a 和 b,如果 a == b,则 a.hashValue == b.hashValue。反之则不然:具有相等哈希值的两个实例未必相等。” 这就是为什么你需要修复你的 == 函数。 - rmaddy
不完全是这样。== 使比较工作。您可以在没有哈希的情况下进行比较。hashValue 使值“可哈希”,从而允许将它们用作字典键或存储在某些其他形式的哈希映射/表中。请注意,Hashable 扩展了 Equatable。这意味着哈希取决于比较,但比较不取决于哈希。有关这两个协议的详细信息,请阅读文档。 - rmaddy
原来如此,这就是为什么如果我说 dict[DateStruct1] = 1dict[DateStruct2] = 2,即使 DateStruct1DateStruct2 可能具有相同的 hashValue,它也不会访问相同的字典条目,因为它还会使用 == 进行比较。 - MarksCode
显示剩余2条评论

18
< p >虽然

    var hashValue: Int 

仍然适用于传统的NSObject继承树,否则已经过时。

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

Hashable是在类中快速进行哈希的现代方式。

根据rmaddy的回答,“==”运算符也必须解决问题,以便正确地表示您的语义。

根据Manish的说法,只需声明即可免费获得结构的Hashable一致性。


这种方法相比三年前@rmaddy的被接受的答案有什么好处?在什么情况下,有人可能更喜欢您的建议? - Jeremy Caney
1
这不是一个利益问题:Maddy的答案是必需的,但不足以满足Hashable的要求。一旦添加==,错误仍然存在,但xcode 11.3到11.5不会为hash(into)创建协议存根。 - Anton Tropashko
1
如果结构体符合Hashable协议并且所有属性也符合Hashable协议(Swift 5.4和Xcode 13.2.1),Swift将自动生成一个hash(into:)方法。 - Manish Nahar

9

如果一个类具有字段(另一个类的类型),那个类应该采用Hashable协议。

例子

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

}

这里,Activity类也必须采用Hashable。


6

3
对于简单的结构体,如果其所有属性都已经是Hashable类型(如IntString等),我们只需声明即可符合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
        }
    }
}

2
您在定义结构体时没有指定Hashable协议: struct DateStruct: Hashable { ... 以下代码来自您的示例,可以在Playground上运行。请注意,这里已修改了您的==操作符:
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

1
public struct HistoryFilePair : Hashable {
    //this
    public static func == (lhs: HistoryFilePair, rhs: HistoryFilePair) -> Bool {
        lhs.commit.oidShort == rhs.commit.oidShort
    }
    
    // this
    public func hash(into hasher: inout Hasher) {
        return hasher.combine(self.commit.oidShort)
    }

    //some code of your struct/class
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接