将值标准化为较小的范围

时间:2013-02-20 06:45:44

标签: c++ linux algorithm

描述

我有一组相当大的(字符串,字符串,字符串)唯一元组(大约40mln但可以变大)。对于每个元组,我计算一个unsigned int值。我希望将这些值存储在某处,所以在生成它们之后它们可以被重用(即使在应用程序关闭之后,因此在内存存储中是不可能的,不幸的是数据库也是如此)。

首先,我将它们作为元组(字符串,字符串,字符串,值)存储在文件中,但读取40mln记录需要时间(我几乎立即需要它)。

我决定首先计算每个(字符串,字符串,字符串)元组的哈希值,然后将其标准化为[0,n](其中 n 是值的数量)并仅存储二进制文件中的按排序顺序排序(按规范化哈希值排序)。之后,我可以简单地mmap()这个文件并使用mmap [normalize(hash(string,string,string))]获取值。

我的哈希函数非常简单,但速度快,适用于我的情况(没有发现任何冲突):

concatenatedString = s1+"."+s2+"."+s3
unsigned int hash = 31;
for(int i = 0; i < concatenatedString.length(); i++) {
  hash = hash * 101 + (unsigned int) concatenatedString[i];
}

与标准化相同(直截了当):

((long) n * hash) / max_value

n - 我的标准化范围的上限(所以大约40mln,我取n而不是(n - lower_bound)因为lowe_bound = 0)

max_value - 旧集合的最大值(在我的情况下为UINT_MAX,min_value = 0,所以我不将其包含在等式中)

问题

我的哈希函数在0到4,294,967,295(unsigned int)的范围内不会产生均匀分布的值(看不出它怎么做)。因为归一化后我有相当多的冲突导致数据丢失(在相同的数组索引下覆盖值)。

有没有聪明的方法可以做我想做但没有那些碰撞的事情?

我完全清楚可能会发生一些碰撞。事情就是我的方法,他们往往经常发生。我的散列范围比我的元素数量大100倍,所以我猜可能有办法做到这一点,但我还没弄清楚如何。

解决方案 最后,我改变了我的散列murmur哈希,改变了我的归一化的方法来简单的“模newRange”和更改的文件(我现在存储的所有数据(字符串字符串字符串值))的格式 - 该文件是相当大的,但现在由于我能够实现一个简单的碰撞检测机制(双重散列)。

4 个答案:

答案 0 :(得分:4)

我很惊讶你在规范化哈希值范围之前没有遇到冲突。看起来你正在使用非标准化范围[0,2 ^ 32]。查看生日问题图表here,与4 * 10 ^ 7元素发生碰撞的概率应高于75%。在任何情况下,将散列输出归一化到等于该组元素的大小的范围实际上保证了非平凡数量的冲突。除非你愿意为你的哈希值使用计数器,否则我不知道你将如何避免这种情况。

编辑:看到你的编辑。即使范围是元素数量的100倍(大约4 * 10 * 9),您仍然可能会遇到很多碰撞。如上所述,一次或多次碰撞的概率远远超过75%。

我建议有两件事:

选择不同的哈希函数

如您所述,虽然您的哈希函数很快,但它不会在[0,2 ^ 32]范围内随机分配值。有几个散列函数都很快,并且可以更好地在散列函数范围内分配散列值。我过去使用的是MurmurHash

使用更大的范围

使用更大的范围可以降低碰撞的风险。再看一下图表here,看起来64位应足以将碰撞风险降低到10 ^ -6以下。在这种情况下,MurmurHash64A和MurmurHash64B变体将非常有用。

答案 1 :(得分:1)

并不总是可以将哈希值标准化为唯一的[0..n]值。

我可以向你推荐两种方法:

  1. 对文件进行排序并使用二进制搜索而不是地图。 (LOGN 复杂度)
  2. 使用索引折叠第二个文件并在范围内实现哈希表 [0..5n](5n可能被任何其他数字改变,比n更重要。)

答案 2 :(得分:1)

你说你正在使用它进行规范化:

((unsigned int) n * hash) / max_value

并且您说max_valueUINT_MAX

  

“max_value - 旧集合的最大值(UINT_MAX”

hash被声明为unsigned int

嗯,你知道,那么上面只能产生值0和1,保证碰撞。

您是否了解C ++中整数和浮点除法 之间的 差异?

如果没有,那么我建议获得a C++ textbook


顺便说一句,演员表,如“(unsigned int)blah”,是创建错误的可靠方法。他们告诉编译器闭嘴,不告诉你可能存在的问题,因为,你告诉它,你知道的更好。但你没有。

答案 3 :(得分:0)

据我所知,你需要一个独特的哈希(实际上是不可能的:) :):

在Java中,String.hashCode()为您提供32位哈希码。

如果你想要(比方说)64位哈希码,你可以自己轻松实现它。

如果需要String的加密哈希,Java加密库包括MD5,SHA-1等的实现。您通常需要将String转换为字节数组,然后将其提供给哈希生成器/摘要生成器。例如,请参阅@BorisPavlović的回答。

如果你想要一个独特的哈希码,那你就不走运了。哈希和哈希码是非唯一的。

长度为N的Java字符串具有65536 ^ N个可能的状态,并且需要具有16 * N位的整数来表示所有可能的值。如果你编写一个产生较小范围的整数(例如小于16 * N位)的哈希函数,你最终会发现多个String哈希到同一个整数的情况;即,哈希码不能是唯一的。这被称为鸽笼原则,并且有一个直接的数学证明。 (你不能打数学并获胜!)