集的包含方法在不同时间返回不同的值

时间:2018-12-02 18:25:19

标签: swift iequatable hashable

我在考虑Swift如何确保Set的唯一性,因为我免费将obj的一个从Equatable转换为Hashable,所以我想到了这个简单的游乐场

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)

所以我的第一个问题是:

每次在集合中插入新的obj时,都会调用static func ==方法吗?

我的问题来自这个想法:

对于Equatable obj,要做出此决定,确保两个obj相同的唯一方法是询问static func ==的结果。

对于Hashable obj,一种更快的方法是比较hashValue……但是,就像我的情况一样,默认实现将同时使用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实现的情况下,有时会调用==而有时却没有调用? 苹果应该避免这种意外行为吗?

Returns false returns true

1 个答案:

答案 0 :(得分:1)

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协议的要求:

  

两个相等的实例必须以相同的顺序将相同的值提供给hash(into :)中的Hasher。

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

测试中可能发生的 是,哈希值second用于确定该值所在的集合的“桶” 将被存储。这可能与存储first的存储桶不同。 –但这是一个实现细节:未定义行为是未定义行为,它可能导致意外结果甚至 运行时错误。

实施

var hashValue: Int {
    return string.hashValue
}

或者(从Swift 4.2开始)

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

修复了违反规则的情况,因此使您的代码表现出预期的效果。