Note:下面的问题是相关的,但它们和相关资源似乎都不能完全回答我的问题,尤其是关于对collections of objects实施平等测试的问题.


出身背景

NSObject提供了-hash(返回实例地址,如(NSUInteger)self)和-isEqual:(返回NO,除非接收器地址和参数相同)的default个实现.这些方法被设计为在必要时被重写,但文档明确指出,您应该同时提供这两种方法,或者两者都不提供.此外,如果-isEqual:针对两个对象返回YES,那么针对这些对象must-hash的结果将是相同的.如果不是,那么当将本应相同的对象(例如-compare:返回NSOrderedSame的两个字符串实例)添加到Cocoa集合或直接进行比较时,就会出现问题.

上下文

我开发了CHDataStructures.framework,一个Objective-C数据 struct 的开源库.我已经实现了许多集合,目前正在改进和增强它们的功能.我想添加的一个功能是能够比较集合是否与其他集合相等.

这些比较应该只考虑两个集合中存在的对象(包括排序,如果适用的话),而不只是比较内存地址.这种方法在可可中有相当多的先例,通常使用单独的方法,包括以下方法:

我想让我的自定义集合对相等性测试具有健壮性,这样它们就可以安全地(且可预测地)添加到其他集合中,并允许其他集合(如NSSet)确定两个集合是否相等/等效/重复.

问题

-isEqualTo...:方法本身工作得很好,但是定义这些方法的类通常也会重写-isEqual:以调用[self isEqualTo...:],如果参数与接收方属于同一类(或者可能是子类),或者[super isEqual:]属于其他类.这意味着该类还必须定义-hash,以便为具有相同内容的不同实例返回相同的值.

此外,苹果-hash年的文件规定如下:(强调我的)

"If a mutable object is added to a collection that uses hash values to determine the object's position in the collection, the value returned by the hash method of the object must not change while the object is in the collection. Therefore, 100 the hash method must not rely on any of the object's internal state information 101 you must make sure the object's internal state information does not change while the object is in the collection. Thus, for example, a mutable dictionary can be put in a hash table but you must not change it while it is in there. (Note that it can be difficult to know whether or not a given object is in a collection.)"

Edit: I definitely understand why this is necessary and totally agree with the reasoning — I mentioned it here to provide additional context, and skirted the topic of why it's the case for the sake of brevity.

我的所有集合都是可变的,散列必须考虑至少some的内容,所以这里唯一的 Select 是考虑它是一个编程错误来改变在另一个集合中存储的集合.(我的Collection 都采用了NSCopying本,所以像NSDictionary这样的Collection 可以成功地复制一本用作密钥等.)

对我来说,实现-isEqual:-hash是有意义的,因为(例如)我的一个类的间接用户可能不知道要调用的特定-isEqualTo...:方法,甚至不关心两个对象是否是同一个类的实例.他们应该能够对id类型的任何变量调用-isEqual:-hash,并得到预期的结果.

-isEqual:不同(-isEqual:可以访问两个被比较的实例),-hash必须"盲目"返回结果,只能访问特定实例中的数据因为它不知道散列的用途,所以all个可能的实例的结果必须一致,这些实例应被视为相等/相同,并且必须始终与-isEqual:一致.(Edit: This has been debunked by the answers below, and it certainly makes life easier.)此外,编写好的散列函数非常重要——保证唯一性是一个挑战,尤其是当您只有一个整数(32/64位)来表示它时.

问题

  1. 在对集合执行相等比较时,是否有最佳做法?
  2. 在Objective-C和Cocoa-esque系列中有什么特别之处需要计划吗?
  3. 对于单元测试-hash,有没有可靠的方法?
  4. 对于包含任意类型元素的集合,有没有关于实现-hash以同意-isEqual:的建议?我应该知道哪些trap ?(Edit:并不像我最初想的那样有问题——正如@kperryua所指出的,"等于-hash的值意味着-isEqual:".)

Edit: I should have clarified that I'm not confused about how to implement -isEqual: or -isEqualTo...: for collections, that's straightforward. I think my confusion stemmed mainly from (mistakenly) thinking that -hash MUST return a different value if -isEqual: returns NO. Having done cryptography in the past, I was thinking that hashes for different values MUST be different. However, the answers below made me realize that a "good" hash function is really about minimizing bucket collisions and chaining for collections that use 100. While unique hashes are preferable, they are not a strict requirement.

推荐答案

我认为,试图想出一些通常有用的哈希函数来为集合生成唯一的哈希值是徒劳的.U62建议将所有内容的哈希值组合在一起,这不会很好地扩展,因为它会使哈希函数O(n).哈希函数实际上应该是O(1),以确保良好的性能,否则哈希函数的目的就失败了.(考虑常见的可可 struct ,即含有数组和其他字典的词典,可能是恶心的.如果收集哈希函数为O(n),那么try 一个大的PLIST的顶层字典的散列将是极其缓慢的.

我的建议是不要太担心Collection 的散列.正如你所说,-isEqual:意味着等于-hash个值.另一方面,等于-hash的值并不意味着-isEqual:.这一事实为创建简单哈希提供了很大的余地.

不过,如果你真的担心碰撞(而且你在concrete measurements个现实世界中有证据证明这是一件值得担心的事情),你仍然可以在一定程度上遵循U62的建议.例如,您可以获取集合中第一个和/或最后一个元素的哈希值,并将其与集合中的-count个元素相结合.这足以提供一个像样的散列.

我希望这至少能回答你的一个问题.

至于第一点:实施-isEqual:是非常简单的.枚举内容,并在每个元素上判断isEqual:.

有一件事需要注意,这可能会影响你决定为你的Collection 的-hash个功能做什么.Collection 的客户还必须了解-isEqual:-hash的规则.如果您在Collection 的-hash中使用"-hash"内容,如果"isEqual:"和"-hash"内容不一致,您的Collection 将中断.当然,这是客户的错,但这是另一个反对以Collection 内容为基础计算-hash英镑的理由.

2号有点模糊.不知道你在想什么.

Objective-c相关问答推荐

Objective-C 中的实例变量是否默认设置为 nil?

为什么我不应该在 init/dealloc 中使用 Objective C 2.0 访问器?

单元测试私有方法 - 目标 C

UISearchbar 键盘搜索按钮操作

XCTAssert 和 XCTAssertTrue 有什么区别?

使用自动布局以编程方式更改框架

如何在使用 React Native 时实现 SSL 证书固定

如何隐藏/显示导航栏中的右键

在 Cocoa 中你更喜欢 NSInteger 还是 int,为什么?

alloc 和 allocWithZone: 有什么区别?

Electron邮件镇静 iOS 8

Objective-C 中的 super 到底是什么?

如何在 Objective-C 中将 NSString 解析为 BOOL?

主队列上的 performSelectorOnMainThread: 和 dispatch_async() 有什么区别?

为什么 LLDB 不能打印 view.bounds?

cellForRowAtIndexPath 是如何工作的?

通过将另一个字符串重复给定次数来创建 NSString

如何在 NSUserDefaults 中保存 NSMutablearray

如何检测父视图控制器中模态视图控制器的解除?

如何设置字体大小以填充 UILabel 高度?