Swift:对哈希协议使用类的ObjectID会导致set.contains方法中的随机行为。代码有什么问题?

Swift:对哈希协议使用类的ObjectID会导致set.contains方法中的随机行为。代码有什么问题?,swift,set,hashable,Swift,Set,Hashable,我有少量自定义类的实例存储在一个集合中。我需要检查该集合中是否包含某个元素。匹配的条件必须是对象的ID,而不是其内容 为简化起见,假设一个类的唯一属性是integer var,该类的两个不同实例都包含数字1 直接比较这些实例应该返回true,但是当对第一个实例的引用存储在集合中时,如果集合包含对第二个实例的引用,则查询应该返回false 因此,我使用对象的ObjectIdentifier来生成哈希协议所需的哈希函数 据我所知,Swift集合的.contains方法首先使用散列值,在散列冲突的情况

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

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

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

因此,我使用对象的ObjectIdentifier来生成哈希协议所需的哈希函数

据我所知,Swift集合的.contains方法首先使用散列值,在散列冲突的情况下,Equalable方法用作回退

但是在下面的代码中,可以在操场上运行,我得到了randum结果:

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
classmyclass:Hashable{
变量编号:Int
初始值(u编号:Int){
self.number=number
}
静态函数==(lhs:MyClass,rhs:MyClass)->Bool{
返回lhs.number==rhs.number
}
func散列(放入散列程序:inout散列程序){
hasher.combine(ObjectIdentifier(self))
}
}
var mySet:Set=[]
设number1=MyClass(1)
设secondNumber1=MyClass(1)
number1==secondNumber1//true:整数值相等,包装类也相等
number1==secondNumber1//false:两个不同的实例
mySet.insert(编号1)
mySet.contains(number1)//true
mySet.contains(secondNumber1)//应为false,但在运行之间会随机更改
如果在XCode游乐场中运行上述代码并手动重新启动游乐场执行,则每次运行的最后一行会得到不同的结果。期望的行为是每次都得到“假”


那么,实现所描述的行为的正确方法是什么呢?

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

您的
mySet.contains(secondNumber1)
行失败,因为
secondNumber2
可能与
number1
发生哈希冲突。是否发生碰撞尚未定义,因为。如果确实发生哈希冲突,那么您的相等运算符(
=
)会错误地将
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))
    }
}
struct IdentityWrapper{
让对象:T
init(uObject:T){self.object=object}
}
扩展标识说话者:可均衡{
静态函数==(左:IdentityWrapper,右:IdentityWrapper)->Bool{
返回lhs.object===rhs.object
}
}
扩展标识rapper:Hashable{
func散列(放入散列程序:inout散列程序){
hasher.combine(ObjectIdentifier(self.object))
}
}
在集合中使用IdentityWrapper需要在与集合交互之前手动包裹对象。它的性能很好(因为struct不需要任何数组分配),而且很可能该struct完全是内联的,但它可能有点烦人。或者,您可以实现一个
结构标识集
,它只包装一个
,将包装代码折叠起来

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
classmyclass:Hashable{
变量编号:Int
初始值(u编号:Int){
self.number=number
}
//基于价值的平等
静态函数==(lhs:MyClass,rhs:MyClass)->Bool{
返回lhs.number==rhs.number
}
//基于值的哈希
func散列(放入散列程序:inout散列程序){
hasher.combine(self.number)
}
}
var mySet:Set=[]
设number1=MyClass(1)
设secondNumber1=MyClass(1)
number1==secondNumber1//true:整数值相等,包装类也相等
number1==secondNumber1//false:两个不同的实例
mySet.insert(IdentityWrapper(编号1))
打印(mySet.contains(IdentityWrapper(number1))//true
打印(mySet.contains(IdentityWrapper(secondNumber1))//false

无需花费大量时间查看。我想知道,您是否应该直接重写
hash
而不是
func-hash(into-hasher:inout-hasher),并返回
ObjectIdentifier(self).hash
。既然如此,为什么不改为使用
hasher.combine(number)
“两个相等的实例必须以相同的顺序将相同的值以散列形式(输入:)馈送给哈希器。”。您需要一个不同于
Hashable
Set
的解决方案。当我使用
code
Hasher.combine(数字)时)
code
对于相同的数字,我会得到相同的哈希值,但正如我在第一段中所写的那样,我想检查set.contains方法的标识,而不是相等性。@rmaddy好的,但是您对该解决方案有什么提示吗?查看
NSHashTable
类。您可以基于对象标识创建一个哈希值。不是吗哈希冲突的问题是相同的吗?显然,从ObjectID生成的哈希值很容易发生冲突,但令人惊讶的是。“哈希冲突的问题不是相同的吗?”哈希冲突不是问题所在。是相等匹配中的假阳性导致了错误。”从ObjectID生成的hashValue容易发生冲突,令人惊讶的是,“为什么会如此令人惊讶?想象一个假设的产品。一个产品的hash就是它的“类别”。T