Set 的包含方法 returns 不同时间的不同值

Set's contains method returns different value at different time

我在考虑 Swift 如何确保 Set 的唯一性,因为我已经免费将我的一个对象从 Equatable 变成了 Hashable,所以我想到了这个简单的 Playground

struct SimpleStruct: Hashable {
    let string: String
    let number: Int

    static func == (lhs: SimpleStruct, rhs: SimpleStruct) -> Bool {
        let areEqual = lhs.string == rhs.string
        print(lhs, rhs, areEqual)
        return areEqual
    }
}

var set = Set<SimpleStruct>()
let first = SimpleStruct(string: "a", number: 2)
set.insert(first)

所以我的第一个问题是:

每当我在集合中插入一个新对象时,是否会调用 static func == 方法?

我的问题来自这个想法:

对于Equatable obj,为了做出这个决定,唯一保证两个obj相同的方法就是询问static func ==.

的结果

对于 Hashable obj,更快的方法是比较 hashValues...但是,就像我的情况一样,默认实现将同时使用 stringnumber,与 == 逻辑相反。

所以,为了测试 Set 的行为,我刚刚添加了一个打印语句。

我发现有时我会收到打印语句,有时却没有。就像有时候 hashValue 不足以做出这个决定......所以这个方法并不是每次都被调用。 奇怪...

所以我尝试添加两个相等的对象,想知道 set.contains

的结果是什么
let second = SimpleStruct(string: "a", number: 3)
print(first == second) // returns true
set.contains(second)

奇迹中的奇迹,在操场上发射了几次,我得到了不同的结果,这可能会导致不可预测的结果...... 添加

var hashValue: Int {
    return string.hashValue
}

它消除了任何意想不到的结果,但我的疑问是:

为什么,如果没有自定义 hashValue 实现,== 有时会被调用,有时却不会? Apple 应该避免这种意外行为吗?

Hashable 要求的综合实现使用所有存储的 struct 的属性,在您的例子中是 stringnumber。您的实施 == 仅基于字符串:

let first = SimpleStruct(string: "a", number: 2)
let second = SimpleStruct(string: "a", number: 3)

print(first == second) // true
print(first.hashValue == second.hashValue) // false

这违反了 Hashable 协议的要求:

Two instances that are equal must feed the same values to Hasher in hash(into:), in the same order.

并导致未定义的行为。 (并且由于散列值是随机的 自 Swift 4.2 起,每个程序的行为可能会有所不同 运行。)

在您的测试中可能发生的是 second 的散列值用于确定集合的“桶”,其中的值 将被存储。这可能是也可能不是存储 first 的同一个桶。 – 但那是一个实现细节:未定义的行为就是未定义的行为,它可能导致意想不到的结果甚至 运行时间错误。

正在实施

var hashValue: Int {
    return string.hashValue
}

或者(从 Swift 4.2 开始)

func hash(into hasher: inout Hasher) {
    hasher.combine(string)
}

修复了违反规则的问题,从而使您的代码按预期运行。