研究!rsc公司

关于编程的想法和链接,通过

RSS(RSS)

随机哈希函数
发布于2012年4月1日星期日。

特定哈希表的哈希函数应该总是确定性的,对吗?至少,直到几周前,当我能够修复一个调用时出现性能问题兰特在散列函数中。

哈希表的性能与其哈希函数一样好,哈希函数理想地满足任何密钥对的两个属性k个1,k个2:

  1. 如果k个1==k个2,哈希(k个1)==哈希(k个2).
  2. 如果k个1!=k个2,应该是hash(k个1) != 散列(k个2).

通常,遵循规则1将禁止在计算时使用随机位散列,因为如果再次传入同一个密钥,您将使用不同的随机位并获取不同的哈希值。这就是为什么我要打电话兰特在一个哈希函数太令人惊讶了。

如果哈希函数违反了规则1,哈希表就会中断:您找不到任何东西你插进去了,因为你找错了地方。如果哈希函数满足规则1但违反规则2(例如,“return 42”),由于大量的散列冲突,散列表将很慢。你仍然可以找到你放进去的东西,但你也可以使用列表。

规则1的措辞非常重要。简单地说是不够的“哈希(k个1)==哈希(k个1)“,因为这不考虑键相等的定义。如果要构建区分大小写的哈希表,case-preserving字符串键,然后“HELLO”和“HELLO”需要散列为相同的值。事实上,“散列(k个1)==哈希(k个1)“是甚至完全没有必要。怎么可能有必要吗?通过颠倒规则1,散列(k个1)和散列(k个1)可以不相等如果k个1!=k个1,也就是说,如果k个1不等于自身。

这怎么会发生?如果k个1是浮点值NaN(不是数字),按照惯例,它不等于任何东西,甚至不等于它本身。

好吧,但为什么要麻烦呢?好吧,记住规则2。由于NaN!=不,应该是很可能是散列(NaN)!=哈希(NaN),否则哈希表的性能会很差。这很奇怪:相同的输入被散列了两次,我们应该(至少可能)返回不同的散列值。由于输入是相同的,我们需要一个外部熵源,比如兰特.

如果你没有呢?如果有人可以反复诱骗您在NaN下存储内容:

$猫保姆.py#!/usr/bin/python导入timeit定义内部版本(n):m={}对于范围(n)内的i:m[浮点数(“nan”)]=1n=1对于范围(20)内的i:打印“%6d%10.6f”%(n,timeit.timeit('build('+str(n)+')','来自__main__import构建',编号=1))n*=2$python nan.py1   0.0000062   0.0000044   0.0000048   0.00000816   0.00001132   0.00002864   0.000072128   0.000239256   0.000840512   0.0033391024   0.0126122048   0.0503314096   0.2009658192   1.03259616384   4.65748132768  22.75896365536  91.899054$

这里的行为是二次的:输入大小加倍,运行时间加倍。您可以运行等效围棋程序在围棋场上。它具有NaN固定并以线性时间运行。(在操场上,墙上的时间静止不动,但你可以看到它正在远处执行少于100秒。在本地运行以进行实际计时。)

现在,您可以认为在哈希表中放置NaN是一个愚蠢的想法,而且处理NaN!=哈希表中的NaN也是一个愚蠢的想法,你在这两方面都是对的。

但其他选择更糟糕:

最一致的做法是接受NaN的含义NaN(不适用):m[NaN]=1总是创建一个新的散列表元素(因为键不相等读取m[NaN]永远找不到任何数据(原因相同),然后迭代哈希表生成每个插入的NaN条目。

NaN周围的行为总是令人惊讶,但如果NaN!=其他地方没有,你能做的最不令人惊讶的事就是让你的哈希表尊重这一点。要做到这一点,很可能需要哈希(NaN)!=散列(NaN)。您可能已经有了一个自定义浮点散列函数因此,+0和−0被视为相同的值。继续,打电话兰特对于NaN。

(注意:这与哈希表性能问题不同于2011年12月发行。在这种情况下通过制作每个不同的表来解决普通数据上的冲突使用随机选择的哈希函数;内部没有随机性函数本身。)