我需要探索特定应用程序的整数哈希主题。我有一些要求:
在这里解释一下应用程序......我在一个非常受内存限制的环境中运行。我打算不允许碰撞。也就是说,如果与表中的现有值发生冲突,则插入操作将失败。没关系。我不需要每个插件都能成功。我准备做出有利于空间和速度的交易。现在关键是这一点,当我将值存储在表中时,我需要绝对最小化所表示的位数。我希望的基本上是:
如果我哈希值k,我可以立即缩小我存储到原始域的一小部分。如果散列是“半可逆的”并且如果我可以枚举所有可能的域元素散列到k,那么我可以对它们进行排序并为每种可能性分配序数。然后我想存储那么小的序数而不是原始值,这将需要更少的位。然后我应该能够通过枚举存储序数i的第i种可能性来完全逆转这一点。
对逆集g(k)大小的严格限制的重要性是因为我需要知道每个序数需要分配多少位,我想通过分配相同的数字来保持相对简单每个表条目的位数。是。我可能会在小于一个字节的值上工作。原始域的大小相对较小。
我对你的任何想法和任何人可能参考的例子感兴趣。我认为这应该是可行的,但我想了解一系列可能的解决方案。
提前致谢! 标记
答案 0 :(得分:2)
在0..(n-1)
域中应用一些双射以稍微改变一下。如果n
是素数,这将特别容易,因为在这种情况下,您可以将模运算视为一个字段,并执行各种不错的数学函数。可以根据您的需要均匀分配数字的一件事可能是乘以固定数字c
,然后是模数:
a ↦ (c*a) mod n
您必须选择c
,使其成为n
的互质,即gcd(c,n)=1
。如果n
是素数,那么只要c≠0
这是微不足道的,如果n
是2的幂,那么任何奇数都可以满足要求。这种互易性条件确保存在另一个d
c
c*d ≡ 1 (mod n)
d
,即它满足c
,因此乘以n
将撤消效果乘以n
。你可以例如在Java或inverse中使用BigInteger.modInverse
来计算此数字。
如果你的c
是2的幂,那么你可以避免模运算(以及需要的时间),而是做简单的位掩码操作。但即使对于d
的其他值,您有时也可以提出避免泛型除法运算的方案。当您选择c
(以及d
时,您可以这样做,(25*a)&1023
和// n = 1024
// c = 25 = 16+8+1
// d = 41 = 32+8+1
static unsigned shuffle(unsigned a) {
return (a + (a << 3) + (a << 4)) & 1023;
}
static unsigned unshuffle(unsigned a) {
return (a + (a << 3) + (a << 5)) & 1023;
}
只有很少的非零位。然后乘法可能用位移和加法表示。您的优化编译器应该为您处理,只要您确保这些数字是编译时常量。
这是一个使这种优化明确的例子。请注意,以这种方式编写代码不是必要的:通常写入n
之类的内容就足够了。
0..(n-1)
另一种适用于0..(k-1)
是2的幂的情况的混洗方法是使用位移,掩码和xors的某些组合来修改该值。这可以与上面的乘法方法相结合,或者在乘法之前或之后进行比特,或者甚至两者。做出选择在很大程度上取决于价值的实际分配。
仍然在lo
范围内的结果值可以分为两个值:一个部分位于0..(ceil(n/k)-1)
范围内,将被称为hi
,另一个位于lo = a mod k
hi = floor(a/k)
范围k
,我称之为lo
。
hi
如果hi
是2的幂,则可以使用位掩码获取lo
,使用位移获得hi
。然后,您可以使用lo
来表示哈希桶,并使用lo
来表示要存储在该桶中的值。具有相同k
值的所有值都会发生冲突,但它们的k
部分将有助于检索实际存储的值。
如果要识别哈希映射的未占用插槽,则应确保在每个插槽中为此目的保留一个特定的hi
值(例如零)。如果您无法在原始值集中实现此预留,则可能需要选择lo
作为2减1的幂,以便您可以存储n
自身的值以表示空单元格。或者您可以交换hi
和lo
的含义,这样您就可以调整a=k*hi+lo
的值以省略一些值。我将在下面的示例中使用它。
要反转这一切,您可以使用密钥0..(n-1)
和存储的值n=4032
,将它们合并到k=64
范围内的值n/k=63
,然后撤消最初改组以恢复原来的价值。
这个例子适用于避免所有乘法和除法。它在c=577
个广告位上分配d=1153
个值,其中unsigned char bitseq[50] = { 0 };
int store(unsigned a) {
unsigned b, lo, hi, bitpos, byteno, cur;
assert(a < 4032); // a has range 0 .. 0xfbf
// shuffle
b = (a << 9) + (a << 6) + a + 64; // range 0x40 ..0x237dbf
b = (b & 0xfff) + ((b & 0xfff000) >> 6); // range 0x40 .. 0x9d7f
b = (b & 0xfff) + ((b & 0xfff000) >> 6); // range 0x40 .. 0x11ff
b = (b & 0xfff) + ((b & 0xfff000) >> 6); // range 0x40 .. 0xfff
b -= 64; // range 0x00 .. 0xfbf
// split
lo = b & 63; // range 0x00 .. 0x3f
hi = b >> 6; // range 0x00 .. 0x3e
// access bit sequence
bitpos = (lo << 2) + (lo << 1); // range 0x00 .. 0x17a
byteno = (bitpos >> 3); // range 0x00 .. 0x30
bitpos &= 7; // range 0x00 .. 0x7
cur = (((bitseq[byteno + 1] << 8) | bitseq[byteno]) >> bitpos) & 0xff;
if (cur != 0) return 1; // slot already occupied.
cur = hi + 1; // range 0x01 .. 0x3f means occupied
bitseq[byteno] |= (cur << bitpos) & 0xff;
bitseq[byteno + 1] |= ((cur << bitpos) & 0xff00) >> 8;
return 0; // slot was free, value stored
}
void list_all() {
unsigned b, lo, hi, bitpos, byteno, cur;
for (lo = 0; lo != 64; ++lo) {
// access bit sequence
bitpos = (lo << 2) + (lo << 1);
byteno = (bitpos >> 3);
bitpos &= 7;
cur = (((bitseq[byteno + 1] << 8) | bitseq[byteno]) >> bitpos) & 0x3f;
if (cur == 0) continue;
// recombine
hi = cur - 1;
b = (hi << 6) | lo;
// unshuffle
b = (b << 10) + (b << 7) + b + 64;
b = (b & 0xfff) + ((b & 0xfff000) >> 6);
b = (b & 0xfff) + ((b & 0xfff000) >> 6);
b = (b & 0xfff) + ((b & 0xfff000) >> 6);
b -= 64;
// report
printf("%4d was stored in slot %2d using value %2d.\n", b, lo, cur);
}
}
个不同的值加上每个广告位可能的一个特殊空值。它使用{{1}}和{{1}}进行随机播放。
{{1}}
如您所见,可以避免所有乘法和除法运算,以及所有显式模调用。生成的代码是否比每次调用使用单个模调用的代码具有更高的性能仍有待测试。事实上,您需要最多三个减少步骤以避免单个模数,这使得这相当昂贵。
您可以观看Wolfram Alpha。
答案 1 :(得分:0)
没有免费的午餐。
如果您的分布均匀,那么g(k1)
每个n/k
的值都会为k1
。因此,您最终必须存储k*n/k
或n
值,这些值恰好与您开始时的数字相同。
您可能应该寻找压缩算法而不是哈希函数。它会改善你的谷歌charma。
也就是说,在不知道数字分布的情况下很难建议压缩算法。如果它真的是随机的,那么就很难压缩。