【问题标题】:Near perfect hash for guid tostring as dictionary keyguid tostring 作为字典键的近乎完美的哈希
【发布时间】:2013-02-10 20:01:35
【问题描述】:

我要解决的问题:使用 guid 字符串作为 Dictionary(string, someObject) 的键,我希望 完美 对键进行散列...

不确定我是否遗漏了什么...当我使用字典构造函数运行以下测试时,仅传递大小分配我每次运行都会得到 +- 10 次冲突。当我传入 IEqualityComparer 时,只需在字符串上调用 gethashcode,我的测试就顺利通过了!在某些情况下使用 x = 10 次迭代进行多次运行,并且 y 高达一百万!我认为字典正在调整哈希函数,尤其是在处理字符串时?我的机器上没有反射器 :( 所以今晚我不能检查...如果您注释掉交替的字典初始化,您会看到...测试在我的 i7 上运行相对较快。

            [TestMethod]
    public void NearPerfectHashingForGuidStrings()
    {
        int y = 100000;
        int collisions = 0;

        //Dictionary<string, string> list = new Dictionary<string, string>(y, new GuidStringHashing());
        Dictionary<string, string> list = new Dictionary<string, string>(y);
        for (int x = 0; x < 5; x++)
        {

            Enumerable.Range(1, y).ToList().ForEach((h) =>
            {
                list[Guid.NewGuid().ToString()] = h.ToString();
            });

            var hashDuplicates = list.Keys.GroupBy(h => h.GetHashCode())
                .Where(group => group.Count() > 1)
                .Select(group => group.Key).ToList();

            hashDuplicates.ToList().ForEach(v => Debug.WriteLine( x +  "--- " + v));
            collisions += hashDuplicates.Count();
            list.Clear();
        }

        Assert.AreEqual(0, collisions);
    }

        public class GuidStringHashing : IEqualityComparer<string>
{
    public bool Equals(string x, string y)
    {
        return GetHashCode(x) == GetHashCode(y);
    }

    public int GetHashCode(string obj)
    {
        return obj.GetHashCode();
    }
}

【问题讨论】:

  • 这根本不可能。对于 32 位中的每个 128 位 GUID,您不能拥有唯一的哈希。
  • @SLaks 确实有道理,但如果我只指定 100000 个项目并运行测试几百次,那么“基本上”它每次都能通过?
  • 我的意思是使用传入的 IEqualityComparer 测试可以通过,现在每次使用字典中的默认散列函数都会失败,这更符合您的鸽子评论。由于 int32 哈希码,我预计在 2,147,483,647 个项目之后至少会发生 1 次冲突,但我只根据测试处理 100000,因此应该有可能在该域空间上获得“接近”完美的哈希,当我洗有点震惊时在字符串上调用 gethashcode 比默认的字典哈希要好得多
  • 另外,有趣的是:en.wikipedia.org/wiki/Birthday_attack。根据接近末尾的表格,对于 32 位哈希函数,一次冲突概率超过 50% 所需的哈希码数量仅为 77,000。如果有 100,000,您可能会发生多次冲突。
  • Matthew Watson 完全正确;你的期望与现实完全脱节。我有一张在 n 32 位散列后发生不止一次冲突的概率图:blogs.msdn.com/b/ericlippert/archive/2010/03/22/… - 正如你所看到的,只有 10000 个散列时发生一次冲突的可能性已经超过 1%,而在100000.

标签: c# .net


【解决方案1】:

你的测试失败了。

由于您的相等比较器错误地报告恰好具有相同哈希的两个不同 GUID 相等,因此您的字典从一开始就不会存储冲突。

由于pigeonhole principle,根本不可能为超过 232 个项目创建 32 位完美哈希。

【讨论】:

  • 上面的测试没有使用我的IEqualityComparer,它被注释掉了吗?
  • 您的评论“当我传入 IEqualityComparer 时,只需在字符串上调用 gethashcode,我的测试就全部通过了”意味着您使用了错误的相等比较器,并且 - 因为它有错误 - 出现了错误结论。
  • 他的测试代码并不漂亮,但它确实确立了当你创建100,000个GUID,将它们转换为字符串,然后在字符串上调用.GetHashCode(),那么统计上就会出现冲突。
  • 另外,由于“具有相同的哈希码”是等价关系,因此识别所有具有相同哈希码的字符串是“合法的”,即考虑GUID的等价类 哈希码,这就是IEqualityComparer&lt;&gt; 所做的。如果他的相等比较器没有识别出一些不相同的字符串(在通常的字符串比较中),那将只是字符串上的标准身份比较器,然后他不必首先编写它。
【解决方案2】:

这是不可能的。你想要一个完美的散列函数来处理一组未知的键。你可以create perfect hash functions for specific set of keys。您无法创建一个适用于所有键集的完美哈希函数。

原因是“两个耶稣原则”,正如 Mark Knopfler 所说的那样:“两个人说他们是耶稣,其中一个人肯定是错的。” (更广为人知的是“鸽巢原理”)

【讨论】:

  • 我刚刚回复了 Slaks 并且应该对域输入大小更清楚一点 - 根据测试它只有 100000 个项目 - 如果您之前阅读我对 SLaks 的评论应该是可能的,并且我有点震惊,在字符串上调用 gethashcode 比默认的哈希函数好得多? ps +1 为“Mark Knopfler”参考!
  • 如果您的结论是基于上面帖子中的 GuidStringHashing 类的测试,那么您使用的是损坏的比较器。在这种情况下,您的结果将因此毫无意义。
  • 是的,但您不知道哪 100,000 项。您只能在已知的 100,000 个项目集上生成完美的散列函数。当您尝试从字典中快速检索数据时,完美的散列函数很有用 - 您会花时间创建它们,以便快速检索。
  • @MatthewWatson 为什么比较器坏了?
  • 比较器坏了,因为它可以为两个具有相同哈希码但实际上不同的字符串返回true。如果字符串不同,无论它们的哈希码是否相同,它都应该返回 false。使用它会让你看起来好像从来没有在字典中遇到任何冲突(这就是你所看到的!)
【解决方案3】:

完美的哈希码是什么意思?

您的代码有些混乱,尤其是因为您发布了一个未被您的测试方法使用的类 GuidStringHashing

但是您的代码表明,当您制作 100,000 个 GUID,将它们全部转换为字符串,然后获取字符串的哈希码时,经常会发生并非所有哈希码都是不同的。当有超过 40 亿个整数可供选择,而您只生成 100,000 个字符串时,这可能会令人惊讶。

您将GetHashCode() 用于一般字符串,但您的字符串不太一般,它们都类似于

"2315c2a7-7d29-42b1-9696-fe6a9dd72ffd"

所以也许你的哈希码不是最优的。最好将字符串 h 解析回 GUID 并使用其哈希码,如 (new Guid(h)).GetHashCode()

但是,这仍然会产生与 100,000 个 GUID 的冲突。我想你看到的只是birthday paradox

试试这个更简单的代码。这里我在 GUID 上使用 GetHashCode(),所以我们希望整数是非常随机的:

        var set = new HashSet<int>();
        for (int i = 1; true; ++i)
        {
            if (!set.Add(Guid.NewGuid().GetHashCode()))
                Console.WriteLine("Collision, i is: " + i);
        }

我们看到(通过多次运行上述代码)冲突几乎总是在计算出 100,000 个哈希码之前发生。

【讨论】:

    猜你喜欢
    • 2014-09-02
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多