1

我在考虑 Swift 如何确保 Set 的唯一性,因为我已经免费将我的一个 obj 从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 ==每当我在集合中插入新的 obj 时,都会调用该方法吗?

我的问题来自这个想法:

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

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

因此,为了测试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实现的情况下,==有时会被调用,有时却不会?苹果应该避免这种意想不到的行为吗?

返回假 返回真

4

1 回答 1

1

需求的综合实现Hashable使用 a 的所有存储属性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)
}

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

于 2018-12-02T18:50:18.193 回答