以下python代码准确描述了我想要为任意大小(填充)序列实现的目标:
import random
fixed_seed = 1 #generate the same sequence every time with a fixed seed
population = 1000
sample_count = 5 #demonstration number
num_retries = 3 #just enough to show the repeatable behaviour
for trynum in xrange(num_retries):
#generate the fresh/ordered sequence (0->population)...
seq = range(population)
#seed the random number generator the same way every time...
random.seed(fixed_seed)
#shuffle the sequence...
random.shuffle(seq)
#display results for this try...
sample_sequence = [str(x) for x in seq[:sample_count]]
print "try %s: %s..." % (trynum + 1, ", ".join(sample_sequence))
#Sample output...
#try 1: 995, 721, 62, 326, 541...
#try 2: 995, 721, 62, 326, 541...
#try 3: 995, 721, 62, 326, 541...
该方法的问题在于它需要生成整个 首先在内存中排序。对于庞大的人群来说,这可能是一个问题。
请注意,此方法的一个潜在巨大优势是您可以随时选择任何数组位置。
现在 - 如果手头的问题恰好让您将人口规模设置为2的幂 (减1),线性反馈移位寄存器可用于获得可预测的随机序列。 LFSR很整洁,并在wikipedia article上很好地解释了它们。
下面的python代码演示了这一点(我做了一堆唯一性测试,以确保它像宣传的那样工作)。请再次查看wikipedia article,了解代码的工作原理(Galois configuration)。
TAP_MASKS = { #only one needed, but I included 3 to make the code more useful
10: 0x00000240, #taps at 10, 7
16: 0x0000B400, #taps at 16, 14, 13, 11
32: 0xE0000200, #taps at 32, 31, 30, 10
}
def MaxLengthLFSR(seed, register_length):
"Gets next value from seed in max-length LFSR using Galois configuration."
lsb = seed & 1
next_val = seed >> 1
if lsb == 1:
mask = TAP_MASKS[register_length]
next_val ^= mask
return next_val
reglen = 16 #number of bits in register
population = (2**reglen) - 1 #not used, just showing it
fixed_seed = 1 #seed == startval in this case (could randomize in population)
sample_count = 5 #demonstration number
num_retries = 3 #just enough to show the repeatable behaviour
for trynum in xrange(num_retries):
next_val = fixed_seed
seq = [fixed_seed, ]
for x in xrange(sample_count - 1):
next_val = MaxLengthLFSR(next_val, reglen)
seq.append(next_val)
seq = [str(x) for x in seq]
print "try %s: %s..." % (trynum + 1, ", ".join(seq))
#Sample output...
#try 1: 1, 46080, 23040, 11520, 5760...
#try 2: 1, 46080, 23040, 11520, 5760...
#try 3: 1, 46080, 23040, 11520, 5760...
这很好,因为你可以拥有一个巨大的数量,并且可以轻松计算一个可重复的非重复随机数序列,而不需要使用大块内存。
缺点是a)它被限制为大小的“改组”序列(2 ** N-1),并且b)你无法确定随机序列中特定位置的值是什么任意位置。您需要知道特定点的值并从那里走序列。
后者(b)大部分都可以,因为大多数时候你会按顺序生成序列,所以你只需要记住最后一个值。 2限制(a)的力量是一种交易杀手,但......取决于应用程序。
如何实现任意序列长度的最大长度-LFSR非重复结果?
作为奖励,最好有一个解决方案,让您能够知道给定序列位置的数字,而无需将序列遍历到该位置。
注意:如果你想要一个良好的起始LFSR抽头位置,用于最大长度的LFSR(产生整个寄存器的数量而不重复一次),this link is quite good并且每个寄存器大小具有大量的抽头位置(最多32位,无论如何)。
另请注意,我已经看到许多与我的问题和改组/ LFSR密切相关的问题,但它们都与我所追求的完全无关(任意大小的线性序列的可预测的随机播放)。或者至少就我能够理解它们而言,无论如何。
我最近一直在研究Linear Congruential Generators,这似乎很有希望,但我还没能让它们继续工作。我不会再问这个问题,而是如果我弄清楚他们的工作就会发布答案。
答案 0 :(得分:3)
我之前已经写过这个:Secure Permutations with Block Ciphers。简而言之:
步骤2所需的迭代次数平均不超过2次;最糟糕的情况是很高,但极不可能发生。
答案 1 :(得分:1)
首先,请注意,这不是随机序列。它只生成一个固定的重复序列,种子选择你开始的序列中的位置。当然,这与所有PRNG相同,但通常PRNG的周期远大于16位或32位。你用它描述的方式,周期长度等于你正在迭代的项目的数量,所以它所做的只是采取一个“洗牌”的顺序并改变你的位置开始。 “种子”值更像是起始索引而不是种子。
这不是最令人满意的答案,但它可能是实用的:您可以将长度填充到下一个2的幂,并跳过高于实际最大值的任何索引。因此,如果您有5000个项目,则生成超过8192个项目的序列,并丢弃[5000,8191]之间的任何结果。这样做的开销听起来很难看,但从视角来看并不是那么糟糕:因为这最多可以使列表的长度加倍,平均而言你将不得不丢弃两个结果中的一个,因此最坏情况下的平均开销加倍工作量。
以下代码演示了这一点(以及显示更简洁的实现方法)。 MaxLengthLFSR的第三个参数(如果给定)是实际的最大值。您可能希望为更多的大小填写TAP_MASKS,然后选择适合所请求序列长度的最小寄存器大小;这里我们只使用请求的那个,但是如果序列的长度远大于它需要的话,它会起到更大的开销。
TAP_MASKS = { # only one needed, but I included 3 to make the code more useful
10: 0x00000240, # taps at 10, 7
16: 0x0000B400, # taps at 16, 14, 13, 11
32: 0xE0000200, # taps at 32, 31, 30, 10
}
def MaxLengthLFSR(next_val, reglen, max_value=None):
"""Iterate values from seed in max-length LFSR using Galois configuration."""
# Ensure that max_value isn't 0, or we'll infinitely loop without yielding any values.
if max_value is not None:
assert max_value > 0
while True:
if max_value is None or next_val < max_value:
yield next_val
lsb = next_val & 1
next_val = next_val >> 1
if lsb == 1:
mask = TAP_MASKS[reglen]
next_val ^= mask
sample_count = 5 # demonstration number
num_retries = 3 # just enough to show the repeatable behaviour
for trynum in xrange(num_retries):
it = MaxLengthLFSR(1, 16, 2000)
seq = []
for x in xrange(sample_count):
seq.append(next(it))
seq = [str(x) for x in seq]
print "try %s: %s..." % (trynum + 1, ", ".join(seq))