Php 固定范围数的双向散列

Php 固定范围数的双向散列,php,math,random,shuffle,Php,Math,Random,Shuffle,我需要创建一个函数,该函数以一个整数作为范围0-N中的参数,并在相同范围内返回一个看似随机的数字 每个输入编号应始终有一个输出,并且应始终相同 int enc(int x) { x = x + 4799 * 256 * (x % 256); x = x + 8896843; x = x ^ 4777277; return (x + 1073741824) % 16777216; } 这样的函数会产生如下结果: f(1) = 4 f(2) = 1 f(3) = 5 f(4) =

我需要创建一个函数,该函数以一个整数作为范围
0-N
中的参数,并在相同范围内返回一个看似随机的数字

每个输入编号应始终有一个输出,并且应始终相同

int enc(int x) {
  x = x + 4799 * 256 * (x % 256);
  x = x + 8896843;
  x = x ^ 4777277;
  return (x + 1073741824) % 16777216;
}
这样的函数会产生如下结果:

f(1) = 4
f(2) = 1
f(3) = 5
f(4) = 2
f(5) = 3
我相信这可以通过某种哈希算法来实现?我不需要任何复杂的东西,只是不需要太简单的东西,比如
f(1)=2
f(2)=3
等等

最大的问题是我需要这是可逆的。例如,上表应为真正的从左到右以及从右到左,使用不同的函数进行从右到左的转换是可以的

我知道最简单的方法是创建一个数组,将其洗牌,然后将关系存储在db或其他东西中,但由于我需要
N
非常大,如果可能的话,我希望避免这种情况


编辑:对于我的特殊情况,
N
是一个特定的数字,它正好是16777216(64^4)。

我提供了如何在生成研究数据集时“随机”将9位SSN置乱。这不会替换或散列SSN。它对数字进行重新排序。如果您不知道数字被置乱的顺序,则很难将其恢复到正确的顺序。我有一种直觉,这不是发问者真正想要的。所以,如果这个答案被认为是离题的,我很乐意删除它

我知道我有9个数字。因此,我从一个数组开始,该数组按顺序有9个索引值:

$a = array(0,1,2,3,4,5,6,7,8);
现在,我需要将一个我能记住的键转换成一种洗牌数组的方法。对于同一个键,每次洗牌的顺序必须相同。我使用了一些技巧。我用crc32把一个单词变成一个数字。我使用srand/rand获得随机值的可预测顺序。注意:mt_rand不再产生具有相同种子的相同随机数字序列,因此我必须使用rand

srand(crc32("My secret key"));
usort($a, function($a, $b) { return rand(-1,1); });
数组$a仍然有数字0到8,但是它们被洗牌了。如果我使用相同的关键字,我每次都会得到相同的洗牌顺序。这让我每个月重复一遍,得到同样的结果。然后,使用无序数组,我可以从SSN中选择数字。首先,我确保它有9个字符(一些SSN作为整数发送,前导0被省略)。然后,我通过使用$a拾取数字来构建一个屏蔽SSN

$ssn = str_pad($ssn, 9, '0', STR_PAD_LEFT);
$masked_ssn = '';
foreach($a as $i) $masked_ssn.= $ssn{$i};
$masked_ssn现在将具有$ssn中的所有数字,但顺序不同。从技术上讲,有些关键字会使$a在洗牌后成为原始的有序数组,但这种情况非常罕见

希望这是有道理的。如果是这样,你可以做得更快。如果将原始字符串转换为字符数组,则可以对字符数组进行洗牌。你只需要每次重新设定rand的种子

$ssn = "111223333"; // Assume I'm using a proper 9-digit SSN
$a = str_split($ssn);
srand(crc32("My secret key"));
usort($a, function($a, $b) { return rand(-1,1); });
$masked_ssn = implode('', $a);
从运行时的角度来看,这并不是更快,因为rand是一个相当昂贵的函数,在这里运行rand要多得多。如果您像我一样屏蔽数千个值,那么您将希望使用一个仅被洗牌一次的索引数组,而不是对每个值进行洗牌

现在,我如何撤销它?假设我对索引数组使用第一个方法。它将类似于$a={5,3,6,1,0,2,7,8,4}。这些是原始SSN的隐藏顺序索引。因此,我可以轻松地构建原始SSN

$ssn = '000000000'; // I like to define all 9 characters before I start
foreach($a as $i=>$j) $ssn[$j] = $masked_ssn{$i};

正如您所看到的,$i在屏蔽SSN中从0到8计数$j数5,3,6。。。并将屏蔽SSN中的每个值放在原始SSN中的正确位置。

如果范围始终是二的幂(如[016777216]),则可以按照@MarkBaker的建议使用异或。如果范围不是二的幂,那么它就不那么容易工作

$ssn = '000000000'; // I like to define all 9 characters before I start
foreach($a as $i=>$j) $ssn[$j] = $masked_ssn{$i};
你可以使用模N的加法和减法,尽管这两种方法太明显了,所以你必须把它和其他方法结合起来

你也可以做模N乘法,但倒数是很复杂的。为了使它更简单,我们可以分离出底部的八个位,然后将它们相乘,然后以一种不干扰这些位的方式将它们相加,这样我们就可以再次使用它们来倒数运算

我不懂PHP,所以我将给出一个C语言的例子,也许是一样的

int enc(int x) {
  x = x + 4799 * 256 * (x % 256);
  x = x + 8896843;
  x = x ^ 4777277;
  return (x + 1073741824) % 16777216;
}
要解码,请按相反顺序播放操作:

int dec(int x) {
  x = x + 1073741824;
  x = x ^ 4777277;
  x = x - 8896843;
  x = x - 4799 * 256 * (x % 256);
  return x % 16777216;
}
1073741824必须是N的倍数,256必须是N的因子,如果N不是2的幂,那么你就不能(必然)使用exclusive or(
^
在C中是exclusive or,我假设在PHP中也是)。其他数字你可以随意摆弄,也可以随意添加和删除stage


在这两个函数中添加1073741824是为了确保x保持正;这是为了使模运算永远不会给出负结果,即使我们从x中减去了可能会使其在过渡期间变为负的值。

看起来你得到了很好的答案,但仍然有另一个选择。线性同余生成器(LCG)可以提供1对1的映射,使用欧几里德算法已知它是可逆的

Xi = [(A * Xi-1) + C] Mod M
where M = 2^24 = 16,777,216

A = 16,598,013
C = 12,820,163

对于LCG可逆性,请看一下

,如果它需要可逆,那么它就不是散列;为什么不简单地用一个固定值进行位异或运算呢?我经常这样做。我有一组被SSN识别的人。在为研究人员运行报告时,我会将数字置乱。如果需要返回并添加更多数据,我可以对它们进行解读。我可以发布一个d如果您愿意,可以将此函数描述为答案。@MarkBaker您能详细说明一下吗?我尝试了一个简单的$seed^$值,用于从1到100的所有数字,其中34作为种子,它产生了超出范围的数字。您可能希望使用一个种子,使其更接近两个范围的最大值幂,就像给出的示例非常简单一样,但任意范围是一个令人头痛的问题。你能保证它总是两个范围的幂吗?这实际上是一个有趣的方法。但是,有一个问题。你使用的是固定长度,10位数,