【问题标题】:Retrieve known index of a pseudo-random sequence检索伪随机序列的已知索引
【发布时间】:2014-05-08 20:53:53
【问题描述】:

我有一组正好 16,704,200 个独特的对象。我需要构造一个函数f,这样:

  • f(x) 从列表中返回一个看似随机的对象(但对于给定的 x 值,总是相同的对象)

  • f(0)f(16704199) 以看似随机的顺序返回完整的对象集(无重复)

  • f 不需要存储 16,704,200 个有序整数的列表

我查看了几个关于使用伪随机数生成器或线性反馈移位寄存器生成随机数序列的 SO 答案。缺点是找到f(7000) 的值的唯一方法是初始化寄存器,循环7000 次,然后返回数字。 (除非我存储了整个预先生成的序列,如上所述,我不想这样做。)

有没有更适合在随机序列中查找第 7000 个 (xth) 条目的算法?

【问题讨论】:

  • 本质上,您正在寻找自身的 可逆 映射 (0..16_704_199),其中关系是“随机的”。显然,真正的随机已经过时了,如果您不想存储或迭代,大多数 PRNG 都会过时。您也许可以构建一个可逆映射,但随机性可能很简单或很弱 - 这可以接受吗?
  • @NeilSlater 是的,在我的情况下,弱随机性是完全可以的,只要f 的任何给定序列 - 比如说,任何给定的 100 个连续 x 的集合 - 分布有点均匀整个列表。
  • 您也许可以使用“老派”RNG 构建一些东西:en.wikipedia.org/wiki/Linear_congruential_generator - 另见stackoverflow.com/questions/2911432/…
  • 不知道有没有这样的算法满足第2点和第3点,如果原始集合中对象的顺序不能改变。

标签: ruby


【解决方案1】:

您可以使用Linear Congruential Generator - 这种类型的 PRNG 现在被认为非常粗糙,可用于任何需要统计随机性的目的,但在您的情况下确实有一个优势,即可以重复已知大小的特定序列。它也恰好是可逆的,这与您在序列id和选定索引id之间进行1对1映射的要求有关。

首先,选择几个素数,介于总大小 N 的 60% 到 80% 之间。

N = 16_704_200
A =  9_227_917
C = 11_979_739

您可以使用Prime 模块查找您的号码。您甚至可以使用 PRNG 选择它们,并且只存储您需要的素数。

现在有了这些值,就可以实现LCG算法了,就是你想要的f(x)

def lcg x
  ( A * x + C ) % N
end

快速测试:

lcg( 0 )
# => 11979739

lcg( 12345 )
# => 7971104

(0..9).map { |x| lcg( x) }
 # => [ 11979739, 4503456, 13731373, 6255090, 15483007,
 #      8006724, 530441, 9758358, 2282075, 11509992 ]

。 . .好吧,它可能是随机的,如果你将输出作为下一个输入参数反馈,那么你就有了一个“老派”(而且质量非常低)的 PRNG。但您可以将它用于index_id = lcg( sequence_id ),以随机顺序获取您的对象。

它是否将整个输入值集映射到同一组输出值:

(0...N).map { |x| lcg( x ) }.uniq.count
# => 16704200

是的!


虽然你不需要它,但算法可以反过来。操作方法如下:

棘手的一点是找出A 的乘法逆元。 Here is an example of how to do that I found.

AINVERSE = 9257653
# Test it:
( A * AINVERSE ) % N 
# => 1

现在有了这些值,就可以前后实现LCG算法了:

def lcg_fwd x
  ( A * x + C ) % N
end

def lcg_rev x
  ( AINVERSE * ( x - C ) ) % N
end

测试一下:

lcg_fwd( 0 )
# => 11979739
lcg_rev( 11979739 )
# => 0

lcg_fwd( 12345 )
# => 7971104
lcg_rev( 7971104 )
# => 12345

【讨论】:

  • 感谢您的帮助,尼尔!我将此答案标记为正确,因为它(和您的 cmets)使我走上了正确的道路。我实际上正在使用一个更简单的解决方案:f(x) => (x * 9_227_917 + 11_979_739) % 16_704_200。我知道这是非常弱的随机性,但它满足了我的所有要求并且是可索引的(而不是向前/向后)。
  • @Elliot Nelson:这正是我的意思。如果您反复执行x = lcg_fwd( x ),它将成为x 的(弱)PRNG。 . .但否则lcg_fwd(x) 您正在寻找的f(x)。反转它的唯一原因是如果您最终知道您选择的目标,但不知道您打算进入的索引位置。甚至提到反转算法的原因是它是您需要的序列的隐含属性。
【解决方案2】:

也许一个预先播种的 Random 对象可以解决问题?

prng1 = Random.new(1234)
prng1.seed       #=> 1234
prng1.rand(100)  #=> 47
prng1.rand(99)   #=> 83

prng2 = Random.new(prng1.seed)
prng2.rand(100)  #=> 47
prng2.rand(99)   #=> 83

http://www.ruby-doc.org/core-2.1.1/Random.html

如果你选择的值足够大,你会得到唯一的数字:

(1..1_000_000).map {|i| prng1.rand(1_000_000_000_000+i)}.uniq.size
=> 1000000

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-03-14
    • 1970-01-01
    • 2020-09-03
    • 1970-01-01
    • 2021-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多