【问题标题】:Two-way hashing of fixed range numbers固定范围数字的双向散列
【发布时间】:2016-05-07 12:42:03
【问题描述】:

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

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

这样的函数会产生这样的结果:

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

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

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

我知道最简单的方法是创建一个数组,将其打乱,然后将关系存储在数据库或其他东西中,但由于我需要 N 非常大,我想尽可能避免这种情况。

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

【问题讨论】:

  • 如果它需要是可逆的,那么它不是哈希;为什么不简单地进行固定值的按位异或?
  • 我经常这样做。我有一组人被认定为 SSN。在为研究人员编写报告时,我会打乱数字。如果我需要返回并添加更多数据,我可以解读它们。如果您愿意,我可以发布对此类功能的描述作为答案。
  • @MarkBaker 你能详细说明一下吗?我已经为从 1 到 100 的所有数字尝试了一个简单的 $seed ^ $value,其中 34 作为种子,它产生了超出范围的数字。
  • 您可能希望使用更接近最大值的种子
  • 2 的幂范围,就像给出的例子一样非常简单,但是任意范围有点让人头疼。你能保证它总是两个范围的幂吗?

标签: php math random shuffle


【解决方案1】:

如果范围始终是 2 的幂 - 例如 [0,16777216) - 那么您可以按照@MarkBaker 的建议使用异或。如果你的范围不是 2 的幂,它就不会那么容易工作。

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

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

我不懂 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 的幂,那么您不能(必然)使用异或(^ 是异或在 C 中,我也假设在 PHP 中)。您可以随意摆弄其他数字,以及添加和删除阶段。

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

【讨论】:

  • 这看起来可能行得通。我会测试它并稍后报告。
  • 我针对 3 测试范围内的所有数字运行您的解决方案:1. 编码然后解码必须返回原始输入,2. 编码不应该返回重复项,3. 结果应该是在同一范围内。它通过了所有 3。这将是我将使用的解决方案,谢谢!
  • 请注意,这只是一个轻微的混淆,它作为哈希函数有一些弱点。需要做更多的工作才能使其成为一个好的哈希。
【解决方案2】:

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

【讨论】:

  • 这实际上是一个有趣的方法。但是,有一个问题。您使用的是固定长度的 10 位数字,总共有 10000000000 个可能的数字。但我确实有一个限制,即结果数字必须在 0-64^4 (16777216)范围内,您的方法肯定会生成超出范围的数字。附:我应该提到N实际上是一个特定的数字。
【解决方案3】:

看起来你的答案很好,但还有其他选择。线性同余生成器 (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

【讨论】:

    猜你喜欢
    • 2011-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-11
    • 2022-01-24
    • 1970-01-01
    • 2021-11-08
    • 1970-01-01
    相关资源
    最近更新 更多