固定范围数的双向散列

时间:2016-05-05 18:37:39

标签: php math random shuffle

我需要创建一个函数,它在0-N范围内接受一个整数作为参数,并在同一范围内返回一个看似随机的数字。

每个输入数字应始终只有一个输出,并且应始终相同。

这样的功能会产生这样的东西:

f(1) = 4
f(2) = 1
f(3) = 5
f(4) = 2
f(5) = 3

我相信这可以通过某种散列算法来实现吗?我不需要任何复杂的东西,只是不像f(1) = 2f(2) = 3等那么简单。

最大的问题是我需要这是可逆的。例如。上面的表格应该是从左到右,从右到左,使用不同的功能进行从右到左的转换很好。

我知道最简单的方法是创建一个数组,将其洗牌并将关系存储在db或其他内容中,但是因为我需要N非常大,所以我想避免这种情况。可能的。

修改:对于我的特定情况,N是一个特定的数字,它恰好是16777216(64 ^ 4)。

3 个答案:

答案 0 :(得分:2)

如果范围总是2的幂 - 如[0,16777216] - 那么你可以使用exclusive-or或@MarkBaker建议。如果你的范围不是2的幂,它就不会那么容易。

你可以使用模N加法和减法,虽然这些单独太明显了,所以你必须把它与其他东西结合起来。

你也可以进行乘法模N,但是反转这很复杂。为了简单起见,我们可以将底部的8位隔离并乘以它们,并以不会干扰这些位的方式添加它们,这样我们就可以再次使用它们来反转操作。

我不懂PHP,所以我将在C中给出一个例子。也许它是一样的。

_.keyBy

要解码,请按相反顺序播放操作:

int enc(int x) {
  x = x + 4799 * 256 * (x % 256);
  x = x + 8896843;
  x = x ^ 4777277;
  return (x + 1073741824) % 16777216;
}

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

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

答案 1 :(得分:1)

我提议描述在生成研究数据集时我如何“随机”加扰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中的正确位置。

答案 2 :(得分:0)

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

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

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

对于LCG可逆性,请查看Reversible pseudo-random sequence generator