在Objective-C中如何正确地覆盖isEqual: ?“陷阱”似乎是,如果两个对象相等(由isEqual:方法决定),它们必须具有相同的散列值。

Cocoa Fundamentals Guide的Introspection部分确实有一个关于如何重写isEqual:的例子,复制如下,用于一个名为MyWidget的类:

- (BOOL)isEqual:(id)other {
    if (other == self)
        return YES;
    if (!other || ![other isKindOfClass:[self class]])
        return NO;
    return [self isEqualToWidget:other];
}

- (BOOL)isEqualToWidget:(MyWidget *)aWidget {
    if (self == aWidget)
        return YES;
    if (![(id)[self name] isEqual:[aWidget name]])
        return NO;
    if (![[self data] isEqualToData:[aWidget data]])
        return NO;
    return YES;
}

它检查指针是否相等,然后是类是否相等,最后使用isEqualToWidget:比较对象,后者只检查名称和数据属性。这个例子没有说明如何重写哈希。

让我们假设有其他属性不影响平等,比如年龄。难道不应该重写哈希方法,以便只有名称和数据影响哈希吗?如果是,你会怎么做?只是添加名称和数据的散列吗?例如:

- (NSUInteger)hash {
    NSUInteger hash = 0;
    hash += [[self name] hash];
    hash += [[self data] hash];
    return hash;
}

这足够了吗?有更好的技术吗?如果你有基本类型,比如int呢?将它们转换为NSNumber以获得它们的散列?或者像NSRect这样的结构?

(脑屁:最初把“位或”和|=写在一起。意味着添加。)


当前回答

坚持,当然一个更简单的方法来做到这一点是首先覆盖- (NSString)描述,并提供一个字符串表示你的对象状态(你必须在这个字符串中表示你的对象的整个状态)。

然后,只需提供以下哈希的实现:

- (NSUInteger)hash {
    return [[self description] hash];
}

这是基于这样的原则:“如果两个字符串对象相等(由isEqualToString:方法决定),它们必须具有相同的散列值。”

来源:NSString类参考

其他回答

我发现这个线程非常有帮助,提供了我需要的一切来获得我的isEqual:和哈希方法实现了一个捕获。当测试isEqual中的对象实例变量时:示例代码使用:

if (![(id)[self name] isEqual:[aWidget name]])
    return NO;

当我知道在我的单元测试中对象是相同的时,这个反复失败(即返回NO)而没有和错误。原因是,其中一个NSString实例变量是nil,所以上面的语句是:

if (![nil isEqual: nil])
    return NO;

因为nil会响应任何方法,这是完全合法的,但是

[nil isEqual: nil]

返回nil,这是NO,所以当对象和被测试的对象都有一个nil对象时,它们将被认为是不相等的(即,isEqual:将返回NO)。

这个简单的修复是将if语句更改为:

if ([self name] != [aWidget name] && ![(id)[self name] isEqual:[aWidget name]])
    return NO;

这样,如果它们的地址是相同的,无论它们都是nil或都指向同一个对象,它都会跳过方法调用,但如果其中一个不是nil或它们指向不同的对象,则会适当地调用比较器。

我希望这能让一些人少挠头几分钟。

开始

 NSUInteger prime = 31;
 NSUInteger result = 1;

然后对于每一个原始元素

 result = prime * result + var

对于对象,你用0表示nil,否则它们的hashcode。

 result = prime * result + [var hash];

对于布尔值,使用两个不同的值

 result = prime * result + ((var)?1231:1237);

解释与归因

这不是tcurdt的作品,评论要求更多的解释,所以我相信编辑归因是公平的。

This algorithm was popularized in the book "Effective Java", and the relevant chapter can currently be found online here. That book popularized the algorithm, which is now a default in a number of Java applications (including Eclipse). It derived, however, from an even older implementation which is variously attributed to Dan Bernstein or Chris Torek. That older algorithm originally floated around on Usenet, and certain attribution is difficult. For example, there is some interesting commentary in this Apache code (search for their names) that references the original source.

最重要的是,这是一个非常古老,简单的哈希算法。它不是性能最好的,甚至在数学上也没有被证明是一个“好”算法。但它很简单,而且很多人长期使用它,效果很好,所以它有很大的历史支持。

我自己只是在学习Objective-C,所以我不能特别地为这种语言说话,但在我使用的其他语言中,如果两个实例是“相等的”,它们必须返回相同的哈希值——否则当你试图将它们作为哈希表(或任何字典类型的集合)中的键时,你会遇到各种各样的问题。

另一方面,如果两个实例不相等,它们可能具有也可能不具有相同的哈希值——最好不是这样。这就是在哈希表上进行O(1)搜索和O(N)搜索的区别——如果你所有的哈希都发生冲突,你可能会发现搜索你的表并不比搜索一个列表好。

在最佳实践方面,您的哈希应该返回输入值的随机分布。这意味着,例如,如果您有一个double,但您的大多数值倾向于聚集在0到100之间,您需要确保这些值返回的哈希值在整个可能的哈希值范围内均匀分布。这将极大地提高你的表现。

有很多哈希算法,包括这里列出的几个。我尽量避免创建新的哈希算法,因为它可能会有很大的性能影响,所以使用现有的哈希方法并像您在示例中所做的那样按位进行某种组合是避免这种情况的好方法。

这并没有直接回答你的问题,但我之前已经使用MurmurHash来生成哈希:MurmurHash

我想我应该解释一下原因:低语是非常快的……

对关键属性的哈希值进行简单的XOR就足够了 99%的情况下。

例如:

- (NSUInteger)hash
{
    return [self.name hash] ^ [self.data hash];
}

Mattt Thompson在http://nshipster.com/equality/找到了解决方案(他也在他的帖子中提到了这个问题:~)