【问题标题】:Perfect Hash Function for Human Readable Order Codes人类可读订单代码的完美哈希函数
【发布时间】:2012-03-22 00:09:32
【问题描述】:

我正在尝试生成非序列的人类可读订单代码,该订单代码源自(比如说)一个无符号的 32 位内部 id,它从 1 开始,每个新订单都会自动递增。

在下面的示例代码中,每个$hash 是否都是唯一的? (我计划对$hash 进行base34 编码以使其可读。)

<?php
function int_hash($key) {
  $key = ($key^0x47cb8a8c) ^ ($key<<12);
  $key = ($key^0x61a988bc) ^ ($key>>19);
  $key = ($key^0x78d2a3c8) ^ ($key<<5);
  $key = ($key^0x5972b1be) ^ ($key<<9);
  $key = ($key^0x2ea72dfe) ^ ($key<<3);
  $key = ($key^0x5ff1057d) ^ ($key>>16);
  return $key;
}

for($order_id = 1; $order_id <= PHP_INT_MAX; ++$order_id) {
  $hash = int_hash($order_id);
}
?>

如果没有,有没有关于如何替换int_hash的建议?

说,base34 编码 md5($order_id) 的结果对我来说太长了。

【问题讨论】:

  • 是什么让您认为它们将是独一无二的? (又名“为什么要使用这个特定的算法?”)。
  • 我更希望是这样。也许我应该直接问我将如何为无符号的 32 位 int 创建一个完美的散列函数。我希望因为它们只是数字,所以我可以将一组简单的数学运算应用于它们,从而产生 1:1 映射。
  • 您只需要 8GiB 以上的空间(当然还有一些时间)来回答您的问题。
  • php short hash的可能重复
  • @Leigh 谢谢,该问题中的链接非常有帮助。在我自己的研究中,我还遇到了 Skip32(一种基于 Skipjack 的 32 位分组密码),这似乎也很合适。

标签: php encoding hash


【解决方案1】:

在下面的示例代码中,每个 $hash 是否都是唯一的?

几乎。(我猜,这意味着“不,但以一种很容易修复的方式”。)您的功能由一系列独立的步骤组成;当且仅当这些步骤中的每一个都是双射的(可逆的)时,整个函数才是双射的。 (你明白为什么吗?)

现在,每个步骤都有以下形式之一:

  $key = ($key ^ CONSTANT) ^ ($key >> NUM_BITS);
  $key = ($key ^ CONSTANT) ^ ($key << NUM_BITS);

NUM_BITS != 0.

我们实际上可以将它们视为单一形式的变体,将前者视为几乎相当于:

  $key = invert_order_of_bits($key); # clearly bijective
  $constant = invert_order_of_bits(CONSTANT);
  $key = ($key ^ $constant) ^ ($key << NUM_BITS);
  $key = invert_order_of_bits($key); # clearly bijective

所以我们只需要证明这一点:

  $key = ($key ^ CONSTANT) ^ ($key << NUM_BITS);

是双射的。现在,XOR 是可交换的和关联的,所以上面等价于:

  $key = $key ^ ($key << NUM_BITS);
  $key = $key ^ CONSTANT;

(x ^ y) ^ y == x ^ (y ^ y) == x ^ 0 == x,很明显,与一个常数值的异或是可逆的(通过与相同的值重新异或);所以我们要证明的是这是双射的:

  $key = $key ^ ($key << NUM_BITS);

无论何时NUM_BITS != 0

现在,我不是在写一个严格的证明,所以我只给出一个 single 合理的例子来说明如何扭转这一点。假设$key ^ ($key &lt;&lt; 9)

0010 1010 1101 1110 0010 0101 0000 1100

我们如何获得$key?好吧,我们知道$key &lt;&lt; 9的最后九位都是零,所以我们知道$key ^ ($key &lt;&lt; 9)的最后九位和$key的最后九位是一样的。所以$key 看起来像

bbbb bbbb bbbb bbbb bbbb bbb1 0000 1100

所以$key &lt;&lt; 9 看起来像

bbbb bbbb bbbb bb10 0001 1000 0000 0000

所以$key 看起来像

bbbb bbbb bbbb bb00 0011 1101 0000 1100

(通过对$key ^ ($key &lt;&lt; 9)$key &lt;&lt; 9 进行异或运算),所以$key &lt;&lt; 9 看起来像

bbbb b000 0111 1010 0001 1000 0000 0000

所以$key 看起来像

bbbb b010 1010 0100 0011 1101 0000 1100

所以$key &lt;&lt; 9 看起来像

0101 1000 0111 1010 0001 1000 0000 0000

所以$key 看起来像

0111 0010 1010 0100 0011 1101 0000 1100

所以。 . .为什么我说“几乎”而不是“是”?为什么你的散列函数不是完美双射的?这是因为在 PHP 中,位移运算符&gt;&gt;&lt;&lt; 不是相当 对称的,而$key = $key ^ ($key &lt;&lt; NUM_BITS) 是完全可逆的,$key = $key ^ ($key &gt;&gt; NUM_BITS) 不是。 (上面,当我写到这两种类型的步骤“几乎等效”时,我真的意思是“几乎”。这会有所不同!)你看,而&lt;&lt; 像对待任何其他位一样对待符号位,并将其移出(在右侧引入一个零位),&gt;&gt; 特别对待符号位,并“扩展”它:左边带入等于符号位。 (注意,您的问题提到了“无符号 32 位”值,但 PHP 实际上并不支持它;它的按位运算总是在 signed 整数上。)

由于这个符号扩展,如果$key0开头,那么$key &gt;&gt; NUM_BITS0开头,如果$key1开头,那么$key &gt;&gt; NUM_BITS也以$key &gt;&gt; NUM_BITS开头1。无论哪种情况,$key ^ ($key &gt;&gt; NUM_BITS) 都将以0 开头。你已经失去了一点熵。如果你给我$key ^ ($key &gt;&gt; 9),但不告诉我$key 是否为负,那么我能做的最好的就是计算$key 的两个可能值:一个负,一个正或零。

您执行了两个使用右移而不是左移的步骤,因此您丢失了两位熵。 (我稍微挥手——我实际证明的只是你失去至少一位,至多两位——但我有信心,由于这些右移步骤之间的步骤的性质,您实际上会丢失两个完整的位。)对于任何给定的输出值,恰好有四个不同的输入值可以产生它。所以它不是唯一的,但它几乎是唯一的;它很容易通过以下方式修复:

  • 将两个右移步骤改为使用左移;或
  • 将两个右移步骤移到函数的开头,在任何左移步骤之前,并说输出对于 0 到 231-1 之间的输入是唯一的,而不是输入介于 0 和 232-1.

【讨论】:

  • 确实是一个令人印象深刻的答案。尽管我在上一段的第二段中有些迷失。可能想在其中插入一两个换行符:)
  • 谢谢,这很有帮助!我的大脑一直在告诉我,它不可能是独一无二的,它太简单了。 (我见过的其他解决方案是使用某种形式的加密、查找表或乘以素数,请参阅link)。
  • @brightemo:不客气!我也真的很惊讶。起初,当我刚刚开始研究逻辑时,我完全希望找到一种产生碰撞的方法,而不是找到为什么没有碰撞的论据。 :-P
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-03
  • 2019-05-22
  • 2022-11-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多