在可变Cocoa对象上实现-hash的技术

时间:2009-01-14 12:40:28

标签: objective-c cocoa cocoa-touch hash

-hash的文档说,当一个可变对象存储在一个集合中时,它不能改变,同样-isEqual:的文档说-hash值必须相同才能相等对象。

鉴于此,是否有人对实施-hash的最佳方式有任何建议,使其满足这两个条件但实际上是智能计算的(即不仅仅返回0)?有人知道框架提供的类的可变版本是如何做到的吗?

最简单的事情当然是忘记第一个条件(关于它不会改变)并确保我从不会在一个集合中偶然改变一个对象,但我想知道是否有任何更灵活的解决方案

编辑:我在想这里是否有可能维持2个合约(当对象在集合中时,相等的对象具有相等的哈希值,并且哈希值不会改变) m改变对象的内部状态。我倾向于说“不”,除非我做一些愚蠢的事情,就像总是返回哈希值,但这就是我问这个问题的原因。

6 个答案:

答案 0 :(得分:3)

有趣的问题,但我认为你想要的东西在逻辑上是不可能的。假设您从2个对象A和B开始。它们都是不同的,并且它们以不同的哈希码开头。您将两者都添加到某个哈希表。现在,您想要改变A,但是您无法更改哈希码,因为它已经在表中。但是,可以以.equals()B。

的方式更改A.

在这种情况下,您有两个选择,两个都不起作用:

  1. 将A的哈希码更改为等于B.hashcode,这违反了在哈希表中不更改哈希码的约束。
  2. 不要更改哈希码,在这种情况下,A.equals(B)但它们没有相同的哈希码。
  3. 在我看来,如果不使用常量作为哈希码,就没有办法做到这一点。

答案 1 :(得分:2)

我对文档的阅读是hash 的可变对象的值可以(并且可能应该)在发生变异时发生变化,但不应该更改当对象没有发生变异时。因此,要引用的文档部分是“不要改变存储在集合中的对象,因为这会导致它们的hash值发生变化。”

直接引用NSObject documentation for hash

  

如果将可变对象添加到   使用哈希值的集合   确定对象在中的位置   集合,返回的值   对象的哈希方法一定不能   在对象所在的时候改变   采集。 因此,无论是哈希   方法一定不能依赖任何一个   对象的内部状态信息或   你必须确保对象的   内部国家信息没有   在对象所在的时候改变   集合

(强调我的。)

答案 2 :(得分:1)

这里的问题不是如何满足这两个要求,而是你应该满足哪一个要求。在Apple的文档中,明确指出:

  

可变字典可以放在哈希表中,但是当它在那里时你不能改变它。

这就是说,你满足哈希的平等要求似乎更重要。对象的哈希值应始终是检查对象是否等于另一个对象的方法。如果情况并非如此,那么它不是真正的哈希函数。

为了完成我的回答,我将给出一个好的哈希实现的例子。假设您正在为您创建的集合编写-hash的实现。此集合将一组NSObject存储为指针。由于所有NSObject都实现了哈希函数,因此您可以在计算集合的哈希时使用它们的哈希值:

- (NSUInteger)hash {
    NSUInteger theHash = 0;
    for (NSObject * aPtr in self) { // fast enumeration
        theHash ^= [aPtr hash];
    }
    return theHash;
}

这样,包含相同指针(以相同顺序)的两个集合对象将具有相同的散列。

答案 3 :(得分:0)

既然你已经覆盖-isEqual:要进行基于值的比较,你确定你真的需要打扰-hash吗?

我无法猜测你究竟需要什么,但是如果你想在不偏离-isEqual的预期实现的情况下进行基于价值的比较:当哈希相同时只返回YES,更好的方法可能要模仿NSString的-isEqualToString :,所以创建自己的-isEqualToFoo:方法而不是使用或覆盖-isEqual:。

答案 4 :(得分:-1)

这个问题的答案以及避免许多可可虫的关键是:

仔细阅读文档。将每个单词和标点符号放到金色标尺上,然后称重,因为它是世界上最后一颗小麦。

让我们再次阅读文档:

  

如果将可变对象添加到使用哈希值确定集合中 对象的位置 的集合中,则[...]

(重点是我的)。

文档作者的永恒智慧意味着,当您实现诸如字典之类的集合时,请勿使用散列进行定位,因为这可能会发生变化。换句话说,它与在可变的Cocoa对象上实现-hash无关(假设问题提出以来的过去10年中,文档没有发生任何变化,我们所有人都认为这样做)。

这就是字典总是复制其键的原因-这样他们可以保证  哈希值不会改变。

然后您将问一个问题:但是,先生,NSMapTable和类似的东西如何处理?

答案根据文档:

  

“其键或值 可以复制到输入中,或者 可以 使用指针标识进行相等性和散列。”

(再次强调我的意思。)

由于上次我们很容易被文档所迷惑,因此让我们进行一些实验,以了解这些东西的实际工作方式:

NSMutableString *string = [NSMutableString stringWithString:@"so lets mutate this"];
NSString *originalString = string.copy;
NSMapTable *mutableStrings = [NSMapTable strongToStrongObjectsMapTable];
[mutableStrings setObject:originalString forKey:string];

[string appendString:@" into a larger string"];
if ([mutableStrings objectForKey:string] == nil)
    NSLog(@"not found!");

if ([mutableStrings objectForKey:originalString] == nil)
    NSLog(@"Not even the original string is found?");

for (NSString *inCollection in mutableStrings)
{
    NSLog(@"key '%@' : is '%@' (null)", inCollection, [mutableStrings objectForKey:inCollection]);
}
for (NSString *value in NSAllMapTableValues(mutableStrings))
{
    NSLog(@"value exists: %@", value);
}

惊奇!

因此,在这里不使用指针相等,而是将重点放在单词“可能”(在这种情况下表示“可能不会”)上,并在向集合中添加内容时简单地复制哈希值。

(所有这些实际上都很好,因为实现NSHashMap或-hash会非常困难)。

答案 5 :(得分:-2)

在Java中,大多数可变类只是不覆盖Object.hashCode(),因此默认实现返回一个基于对象地址的值,并且不会更改。它可能与Objective C相同。