【问题标题】:A good hashing function for a non-uniform sequence of uniformly distributed 4 bits values?用于均匀分布的 4 位值的非均匀序列的良好散列函数?
【发布时间】:2015-01-28 23:09:15
【问题描述】:

我有一个非常具体的问题:

我在 15x50 网格上均匀分布随机值,我想要散列的样本对应于以任何可能的网格位置为中心的 5x5 单元格的正方形。

因此,样本数量可以从 25(大多数情况下远离边界)到 20、15(靠近边界)再到最少 9(在角落)。

因此,即使像元值是随机的,位置也会在序列长度中引入确定性变化。

哈希表大小很小,通常在 50 到 20 之间。

该函数将在大量随机生成的网格(数百/数千)上运行,并且每个网格可能被调用数千次。网格上的位置可以认为是随机的。

我想要一个可以尽可能均匀分布 15x50 个可能样本的函数。

我试过下面的伪代码:

int32 hash = 0;
int i = 0; // I guess i could take any initial value and even be left uninitialized, but fixing one makes the function deterministic
foreach (value in block)
{
    hash ^= (value << (i%28))
    i++
}
hash %= table_size

但是结果,虽然不是很不平衡,但对我来说似乎不是很顺利。也许是因为样本太小了,但是这种情况使得在更大的样本上运行代码变得很困难,如果一些精通计算机的人已经为我准备好了答案,我宁愿不必编写完整的测试工具:)。

我不确定将值两两配对并使用通用字节散列策略是最佳解决方案,尤其是因为值的数量可能是奇数。

我一直坚持使用第 17 个值来表示离网单元格,但这似乎会引入偏差(来自靠近边界的单元格的序列会有很多“离网”值)。

我也不确定测试各种解决方案效率的最佳方法是什么(例如,我应该生成多少个网格来了解性能)。

【问题讨论】:

  • 如果这些值真的是均匀分布的(并且是独立的),你就不需要散列了。只需取第一个(或最后一个,或任何一个)八个 4 位值,将它们解释为单个无符号 32 位整数,然后对表大小取模。
  • 注意:i 未初始化。注2:一切顺利。但是:移位,不要取模,除了最后。
  • @wildplasser 实际上我可能在开始时是随机的,因为它会被模数限制为 [0..27],但是初始化它会使函数具有确定性(我只是忘了这样做它 :))。至于模数,你是对的,但我发现模数更具可读性。我对性能的兴趣不如对同质性的兴趣。
  • 如果您为四位值的序列/_blocks_ 而非每个四位值寻找哈希函数,请在标题和问题开头附近说明。做随机性测试,如果他们表明哈希值的序列很可能来自一个统一的随机源,那就最好了。 随机输入没有得到均匀分布。请证实the results … do not seem very smooth to me.
  • 也许这就是灰胡子的目的,但这里有一个快速的实验可以尝试。计算介于 0(含)和表大小(不含)之间的均匀随机(加密质量伪随机)数的直方图。这对你来说还不够吗?

标签: algorithm hash


【解决方案1】:

http://www.partow.net/programming/hashfunctions/

这里有一些来自各个领域专家的不同哈希函数。函数是为 8 位值设计的,但我相信您可以根据自己的情况进行扩展。我不知道该建议什么,但我认为其中任何一个都应该比您当前的想法更好。

您提出的当前方法的问题是字段 2^n 中的值是循环的,例如,如果您最后制作 mod 64,您会丢失大部分值,最终结果中仅保留最后 3 个值。

【讨论】:

  • 您在此处链接的页面简洁实用。没有答案直接解决了我的特定问题,但它们都很有用,所以赏金去了信誉得分最低的海报。感谢您的帮助。
【解决方案2】:

尽管您持怀疑态度,但我还是会将它们推向标准哈希函数。 如果它们是随机的(并且相对独立 - 你没有说),那么你可能不需要做太多的工作。在这些情况下,Fowler-Noll-Vo (FNV) 是一个很好的候选者。

FNV 在一系列 8 位输入上运行,而您的输入(逻辑上)是 4 位。 正如你所描述的那样,我什至不用费心地“两两”打包。 如果您想尝试这样做,只需在逻辑上用消息长度填充奇数长度系列(显然减少到 4 位值)。

我不希望这种包装能改善散列。它可以为您节省少量周期,因为它将相对昂贵的*&lt;&lt;| 交换。

尝试两者并报告!

以下是 C 中 FNV1a 的打包和“正常”版本的实现:

#include <inttypes.h>

static const uint32_t sFNVOffsetBasis=2166136261;
static const uint32_t sFNVPrime= 16777619;

const uint32_t FNV1aPacked4Bit(const uint8_t*const pBytes,const size_t pSize) {
    uint32_t rHash=sFNVOffsetBasis;
    for(size_t i=0;i<pSize;i+=2){
        rHash=rHash^(pBytes[i]|(pBytes[i+1]<<4));
        rHash=rHash*sFNVPrime;
    }
    if(pSize%2){//Length is odd. The loop missed the last element.
        rHash=rHash^(pBytes[pSize-1]|((pSize&0x1E)<<3));
        rHash=rHash*sFNVPrime;

    }
    return rHash;
}

const uint32_t FNV1a(const uint8_t*const pBytes,const size_t pSize) {
    uint32_t rHash=sFNVOffsetBasis;
    for(size_t i=0;i<pSize;++i){
        rHash=(rHash^pBytes[i])*sFNVPrime;
    } 
    return rHash;
}

注意:我已经对其进行了编辑,以便在添加长度时跳过第一位。显然,奇数长度的底部位 100% 偏向 1。我不知道长度是如何分布的。放在开头比放在结尾可能更明智。

【讨论】:

  • 甚至是 Fowler-Noll-Vo
  • @greybeard。或者那个也是!我似乎对我的哈希值做了一个哈希值;)。
  • 虽然没有人能完全解决我的问题,但我认为所有 3 个答案都是中肯且有用的,因此我将根据发布者的声誉进行赏金归属。幸运的你。谢谢。
猜你喜欢
  • 2011-04-04
  • 1970-01-01
  • 2011-03-02
  • 2011-08-07
  • 2018-02-06
  • 2014-11-12
  • 2011-11-30
  • 2016-05-04
  • 1970-01-01
相关资源
最近更新 更多