重写isEqual的最佳实践:和散列

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

Cocoa Fundamentals Guide的Introspection部分提供了一个关于如何覆盖isEqual:的例子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:比较对象,它只检查namedata属性。 该示例没有显示的是如何重写hash

假设有其他属性不影响平等,比如age 。 不应该重写hash方法,只有namedata会影响哈希? 如果是这样,你会怎么做? 只需添加namedata的哈希值? 例如:

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

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

Brain fart :最初写了“按位或”,它们和|=一起|=


从...开始

 NSUInteger prime = 31;
 NSUInteger result = 1;

然后为你做的每一个原始

 result = prime * result + var

对于64位,你可能也想换个xor。

 result = prime * result + (int) (var ^ (var >>> 32));

对于你使用0的对象为零,否则使用它们的哈希码。

 result = prime * result + [var hash];

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

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

解释和归属

这不是tcurdt的工作,并且评论需要更多解释,所以我相信对归因的编辑是公平的。

该算法在“Effective Java”一书中得到了推广,相关章节目前可在线查找。 该书推广了该算法,该算法现在是许多Java应用程序(包括Eclipse)中的默认算法。 然而,它来自一个更老的实现,这个实现不同地归因于Dan Bernstein或Chris Torek。 这个较旧的算法最初是围绕Usenet浮现的,而某些归因则很困难。 例如,这个Apache代码中有一些有趣的评论(搜索他们的名字)引用了原始源代码。

底线是,这是一个非常古老的简单哈希算法。 它不是最高性能的算法,它甚至不是数学上证明是“好”的算法。 但这很简单,很多人用了很长时间,效果很好,所以它有很多历史的支持。


我只是自己拾取Objective-C,所以我不能专门为那种语言说话,但在其他语言中,如果两个实例是“相等”,他们必须返回相同的散列 - 否则您将拥有全部尝试将它们用作散列表(或任何字典类型集合)中的键时存在各种各样的问题。

另一方面,如果两个实例不相同,它们可能会或可能不会具有相同的哈希值 - 最好不要。 这是在散列表上进行O(1)搜索和O(N)搜索之间的区别 - 如果所有散列相撞,则可能发现搜索表并不比搜索列表更好。

就最佳实践而言,您的哈希应该为其输入返回值的随机分布。 这意味着,例如,如果您有双精度值,但大多数值趋于在0到100之间进行聚类,则需要确保这些值返回的散列均匀分布在整个可能的散列值范围内。 这将显着提高你的表现。

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


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

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

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

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

并且由于零将回应任何方法,这是完全合法的,但是

[nil isEqual: nil]

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

这个简单的解决方法是将if语句更改为:

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

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

我希望这可以帮助人们在几分钟内搔抓。

链接地址: http://www.djcxy.com/p/58481.html

上一篇: Best practices for overriding isEqual: and hash

下一篇: Help refactoring this nasty Ruby if/else statement