我有一组正好是16,704,200个唯一对象。我需要构造一个函数f
,以便:
f(x)
从列表中返回一个看似随机的对象(但对于给定值x
,始终是相同的对象)
f(0)
到f(16704199)
以看似随机的顺序返回完整的对象集(无重复项)
f
不需要存储16,704,200个有序整数的列表
我已经看过几个关于使用伪随机数发生器或线性反馈移位寄存器生成随机数序列的SO答案。缺点是找到f(7000)
的值的唯一方法是初始化寄存器,循环7000次,然后返回数字。 (除非我存储了整个预先生成的序列,如上所述,我不愿意这样做。)
是否有更适合在随机序列中找到第7000个(xth
)条目的算法?
答案 0 :(得分:3)
你可以使用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
答案 1 :(得分:0)
也许预先播种的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