【问题标题】:Techniques for implementing -hash on mutable Cocoa objects在可变 Cocoa 对象上实现 -hash 的技术
【发布时间】:2010-10-01 08:45:29
【问题描述】:

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

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

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

编辑:我想知道当我' m 改变对象的内部状态。我倾向于说“不”,除非我做一些愚蠢的事情,比如总是为哈希返回 0,但这就是我问这个问题的原因。

【问题讨论】:

  • 猜这是一个老问题,刚刚发现它......但是在集合中用作键的可变对象不是通常被复制的吗?这不是在回避问题吗?
  • @nielsbot:只复制 NSDictionaries 的键。 NSSet 不会复制它的对象,CFDictionarySetValue() API 也不会复制它的键。
  • CFDictionarySetValue 如果你将kCFTypeDictionaryKeyCallbacks 传递给CFDictionaryCreate,不是吗?文档几乎毫无意义......我想一个可变集合 obj 可以,呃,缓存哈希值,这与假设集合中的可变 obj 不会更改其哈希值相同,对吧?
  • @nielsbot: kCFTypeDictionaryKeyCallbacks 只保留密钥。 -[NSMutableDictionary setObject:forKey:] 有点特殊,因为它复制传入的密钥,即使它确实是一个 CFMutableDictionaryRef,它有一个不保留密钥的回调。
  • 是的..你是对的。我想你可以用复制回调替换保留回调......但也许这很恶心。 ...无论如何.... /我回去工作

标签: objective-c cocoa cocoa-touch hash


【解决方案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 会非常困难,否则)。

【讨论】:

    【解决方案2】:

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

    可以将可变字典放入哈希表中,但不能在其中更改它。

    话虽如此,满足哈希的相等性要求似乎更重要。对象的哈希值应该始终是一种检查对象是否等于另一个对象的方法。如果情况并非如此,则它不是真正的哈希函数。

    为了完成我的回答,我将举一个很好的哈希实现示例。假设您正在创建的集合上编写-hash 的实现。该集合将 NSObject 数组存储为指针。由于所有的 NSObject 都实现了 hash 函数,你可以使用它们的 hash 来计算集合的 hash:

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

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • 为此的 NSObject 协议文档明确指出覆盖 -isEqual: 而不是 -hash 是不正确的,并且当类的实例存储在集合中时可能会导致问题。此外,如果两个对象都是字符串,则 NSString 会覆盖 -isEqual: 以调用 -isEqualToString:。请参阅此 Apple 文档:developer.apple.com/documentation/Cocoa/Conceptual/…
      【解决方案4】:

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

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

      1. 将A的hashcode更改为等于B.hashcode,这违反了在哈希表中不更改hashcode的约束。
      2. 不要更改哈希码,在这种情况下 A.equals(B) 但它们没有相同的哈希码。

      在我看来,如果不使用常量作为哈希码,就不可能做到这一点。

      【讨论】:

        【解决方案5】:

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

        直接引用NSObject documentation for hash:

        如果一个可变对象被添加到一个 使用哈希值的集合 确定物体的位置 集合,返回的值 对象的哈希方法不能 当物体在 收藏。 因此,无论是哈希 方法不得依赖于任何 对象的内部状态信息或 你必须确保对象的 内部状态信息不 当物体在 收藏。

        (强调我的。)

        【讨论】:

        • 是的,这就是我读它的方式,但是如果哈希不依赖于内部状态,那么就很难使相等的对象哈希相同。这就是我问这个问题的原因,以了解是否有人有一个聪明的解决方案来将可变对象存储在映射表中。
        • 如果您正在为此寻找巧妙的技巧,我想我无能为力,但请注意文档中说哈希方法不能依赖于内部状态或 i> 必须保证内部状态在集合中不会改变。如果符合后者,则可以使用内部状态。
        • 在重新阅读问题时:您是在问是否有办法保持散列和 isEqual:在对集合中的对象进行变异时保持合同,或者是否可以保持一般合同?我正在回答第二个问题,这可能不是您要问的。
        • 我问的是前者。我将更新问题以使其更清楚
        【解决方案6】:

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

        【讨论】:

        • 除非违反了相等对象需要具有相同哈希的规则。 -isEqual 的默认实现:仅比较指针,但我重写它以对对象中的字段进行基于值的比较。
        猜你喜欢
        • 2011-10-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-09
        • 1970-01-01
        • 1970-01-01
        • 2021-05-16
        相关资源
        最近更新 更多