【问题标题】:Reversible pseudo-random sequence generator可逆伪随机序列发生器
【发布时间】:2010-05-26 08:55:31
【问题描述】:

我想要某种方法来创建一个相当长随机数序列,我可以前后翻阅。就像一台带有“下一个”和“上一个”按钮的机器,它会给你随机数。

像 10 位分辨率(即 0 到 1023 范围内的正整数)之类的东西就足够了,一个 >100k 数字的序列就足够了。这是一个简单的游戏类型应用程序,我不需要加密强度随机性或任何东西,但我希望它感觉相当随机。我有一个有限的内存可用,所以我不能只生成一大块随机数据并通过它。我需要在“交互时间”中获取数字——我可以轻松地花几毫秒思考下一个数字,但不会比这更舒服。最终它将在某种微控制器上运行,可能只是一个 Arduino。

我可以用一个简单的线性同余生成器 (LCG) 来做到这一点。向前走很简单,向后走我必须缓存最近的数字并每隔一段时间存储一些点,这样我就可以从那里重新创建序列。

但也许有一些伪随机生成器可以让你前进和前进?应该可以连接两个线性反馈移位寄存器 (LFSR) 以向不同方向滚动,不是吗?

或者也许我可以通过使用某种散列函数来混淆索引号?我要先试试。

还有其他想法吗?

【问题讨论】:

  • 为什么不直接使用页面索引作为种子?

标签: reverse arduino random prng


【解决方案1】:

我问了very similar question at the tigsource forums

散列

至少在游戏中,散列函数可能会做你想做的事。你可以这样做

class ReversibleRNG {
    int x;
public:
    ReversibleRNG(int seed) : x(seed) {}
    int next(){return yourFavoriteHash(++x);}
    int prev(){return yourFavoriteHash(--x);}
};

可逆线性同余生成器 (lcg)

正如多人指出的那样,lcg 确实是可逆的。在 lcg 中,下一个状态的计算方式如下:

x = (a * prevx + c) mod m

我们可以重新排序:

x ≡ a * prevx + c (mod m)
x - c ≡ a * prevx (mod m)

由于在 lcg 中 a 和 m 被选为互质,我们可以使用扩展的欧几里得算法求逆。

ainverse = extEuclid(a, m).x;
ainverse * (x - c) ≡ ainverse * a * prevx (mod m)
ainverse * (x - c) ≡ prevx (mod m)

这意味着

prevx = ainverse * (x - c) mod m

如果仔细选择m和a,算法可以有2^64的周期

实施

我做了一个header-only implementation of this algorithm,以防有人感兴趣。

【讨论】:

  • 谢谢@bobbaluba - 我发现这些信息很有帮助。我不太擅长数学,所以我尝试创建一个小虚拟指南来反转模块化函数 - 以防像我这样的人将来遇到这个问题:stackoverflow.com/questions/29569927/…
  • 我的印象是散列顺序整数会导致子随机序列。见stackoverflow.com/a/41728178/225186
  • 感谢@bobbaluba 分享代码。我刚刚尝试执行它,与几乎瞬时的前进步骤相比,计算后退步骤大约需要 1.4 秒。这是真的还是我做错了什么?我认为是扩展的欧几里得算法需要这么多处理时间,是吗?
  • @Bill:也许你在编译时禁用了优化? ainverse 是使用常量参数执行的 constexpr 函数的结果,大多数编译器应该足够聪明,可以预先计算是否启用了优化(我在示例中使用带有 -O2 的 g++,这对我来说是即时的)。无论如何,我将上面 repo 中的要求提高到 C++17,它允许 constexpr 变量,我认为这也应该在调试模式下强制编译时间计算。
【解决方案2】:

使用非常简单的对称加密算法是最简单的方法之一。每个随机数都是通过用一些固定密钥加密前一个随机数形成的,然后你只需解密即可。

您可以查看http://en.wikipedia.org/wiki/RC4 的 RC4 - 代码。您可以使用更小的密钥计划来使其完全适合 arduino。

【讨论】:

    【解决方案3】:

    用任意密码和任意密钥加密序列1, 2, 3, ...

    AES 几乎可以在所有最新的系统上使用,而且速度闪电快。

    【讨论】:

      【解决方案4】:

      只需颠倒整数递增序列中的位顺序即可。 例如(8位分辨率):

      • 0 0
      • 1 128
      • 2 64
      • 3 192
      • 4 32

      在序列中向前和向后移动非常容易,并且比调用加密或哈希函数要快得多。它还具有生成最长可能序列的好处。

      它绝对不是加密安全的。这是生成值的散点图(同样是 8 位分辨率):

      您可以很容易地看到模式,尽管它可能对您来说足够“随机”。

      【讨论】:

      • 我没有数学证明,但结果序列看起来像是“次随机”的。 en.wikipedia.org/wiki/Low-discrepancy_sequence 。在许多应用中仍然很有用。
      • @alfC 我的图片确实看起来像Hammersley setWolfram says,您可以通过颠倒位的顺序来生成该集合——我认为这证明您是对的。感谢您的链接!
      • 没错,事实上即使是随机的,也不仅仅是可逆/双向的,而是具有“随机访问”的随机序列。我一直想知道这样的事情是否存在。即在 O(1) 中跳过 N 个随机数(任意方向)。
      • @alfC 如果你想要的只是 O(1) 生成伪随机序列中的第 i 个数字,并且你不关心它的可逆性,那么 r(i, seed) = hash(i + 种子)可以解决问题;只需选择您最喜欢的哈希函数
      • 是的,但这将是次随机的,而不是随机的。我不怀疑 subrandom 是随机访问(O(1) 跳转)。我的问题是是否有随机访问的随机序列。
      【解决方案5】:

      如果线性同余生成器足够好,请使用它。它们很容易可逆。关键是反向发生器也是LCG。 LCG 还可以非常快速地向任何方向(向前和向后)跳跃。

      详情可见The Art of Computer Programming - Volume 2

      特别是第 3.2.1 页第 10 页 TAOCP 的公式 6-8 和练习 5 给出了预期的结果。如果您无法解决该练习,您可以轻松找到解决方案,例如here

      【讨论】:

        【解决方案6】:

        虽然我同意 @BlueRaja 的观点,即您应该只在“计数器模式”下使用 AES,并为您的序列随机或基于时间开始,但 AES 在您的嵌入式情况下可能不可用或不可行。

        我确实找到了讨论如何构建可逆 PRNG 的 this interesting paper;它只有 10 页,并且有大量的代码示例。如果 AES 对您不起作用,请尝试一下。

        【讨论】:

          【解决方案7】:

          你也可以用 LCG 倒退,它只是另一个 LCG,它使用乘数的倒数模模,加上一个合适的增量。

          对于您的小数字,您可以使用蛮力来搜索逆,通常可以使用扩展的 GCD 算法来计算。

          除非您的游戏完全是为了好玩,不涉及任何类型的赌注,否则我会选择加密安全的东西,例如其他人建议的 AES 方法。 LCG 和其他线性随机数生成器无法抵御智能对手。

          【讨论】:

            猜你喜欢
            • 2011-11-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-10-23
            • 2010-11-18
            • 2013-03-03
            • 1970-01-01
            相关资源
            最近更新 更多