gpt4 book ai didi

Swift:将类的 ObjectID 用于可散列协议(protocol)会导致 set.contains 方法中出现随机行为。代码有什么问题?

转载 作者:行者123 更新时间:2023-11-28 05:38:32 24 4
gpt4 key购买 nike

我有少量自定义类的实例存储在一个集合中。我需要检查该集合中是否包含某个元素。匹配条件必须是对象的 ID,而不是其内容。

为简化起见,假设一个类的唯一属性是整数 var,并且该类有两个不同的实例,都包含数字 1。

直接比较这些实例应该返回 true,但是当对第一个实例的引用存储在集合中时,查询集合是否包含对第二个实例的引用应该返回 false。

因此我使用对象的ObjectIdentifier来生成hashable协议(protocol)需要的hash函数。

据我了解,Swift Set 的 .contains 方法首先使用哈希值,在哈希冲突的情况下,equatable 方法用作回退。

但在以下可以在 Playground 上运行的代码中,我得到的结果是随机的:

class MyClass: Hashable {
var number: Int
init(_ number: Int) {
self.number = number
}
static func == (lhs: MyClass, rhs: MyClass) -> Bool {
return lhs.number == rhs.number
}
func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
}

var mySet: Set<MyClass> = []

let number1 = MyClass(1)
let secondNumber1 = MyClass(1)

number1 == secondNumber1 // true: integer values are equal, so are the wrapping classes
number1 === secondNumber1 // false: two different instances

mySet.insert(number1)

mySet.contains(number1) // true
mySet.contains(secondNumber1) // should be false but randomly changes between runs

如果您在 XCode Playground 中运行上述代码并手动重新启动 playground 执行,那么每次运行的最后一行都会产生不同的结果。期望的行为是每次都得到“false”。

那么实现上述行为的正确方法是什么?

最佳答案

简单地说,Set依赖func hash(into hasher: inout Hasher)== .一对不匹配的是无效的。在您的情况下,您的平等是基于值(value)的(取决于 self.number ),而您的散列是基于身份的。这是不合法的。

你的 mySet.contains(secondNumber1)线路失败,因为 secondNumber2可能与 number1 发生哈希冲突.是否发生碰撞未定义,因为Swift uses a random seed to defend against hash-flood DDoS attacks .如果确实发生哈希冲突,那么您的相等运算符 ( == ) 会错误地标识为 number1作为 secondNumber1 的匹配项

相反,您可以做的是实现一个包装器结构,它根据对象的身份实现相等性和散列。对象本身可以有自己的基于值的相等性和散列,用于其他目的。

struct IdentityWrapper<T: AnyObject> {
let object: T

init(_ object: T) { self.object = object }
}

extension IdentityWrapper: Equatable {
static func == (lhs: IdentityWrapper, rhs: IdentityWrapper) -> Bool {
return lhs.object === rhs.object
}
}

extension IdentityWrapper: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self.object))
}
}

在集合中使用 IdentityWrapper 需要您在与集合交互之前手动包装对象。它是高性能的(因为 struct 不需要任何数组分配),而且很可能 struct 无论如何都是完全内联的,但它可能有点烦人。或者,您可以实现 struct IdentitySet<T>它只是包装了一个 Set<IdentityWrapper<T>> ,它隐藏了包装代码。

class MyClass: Hashable {
var number: Int

init(_ number: Int) {
self.number = number
}

// Value-based equality
static func == (lhs: MyClass, rhs: MyClass) -> Bool {
return lhs.number == rhs.number
}

// Value-based hashing
func hash(into hasher: inout Hasher) {
hasher.combine(self.number)
}
}

var mySet: Set<IdentityWrapper<MyClass>> = []

let number1 = MyClass(1)
let secondNumber1 = MyClass(1)

number1 == secondNumber1 // true: integer values are equal, so are the wrapping classes
number1 === secondNumber1 // false: two different instances

mySet.insert(IdentityWrapper(number1))

print(mySet.contains(IdentityWrapper(number1))) // true
print(mySet.contains(IdentityWrapper(secondNumber1))) // false

关于Swift:将类的 ObjectID 用于可散列协议(protocol)会导致 set.contains 方法中出现随机行为。代码有什么问题?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57763416/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com