[英]Pseudo-random generation of python list containing binary values with run-length and frequency constraints
我想伪随机地创建一个包含48个条目的列表-24个零和24个-其中相同的值永远不会连续出现3次。 我有以下代码:
import random
l = list()
for i in range(48):
if len(l) < 2:
l.append(random.choice([0,1]))
else:
if l[i-1] == l[i-2]:
if l[i-1] == 0:
l.append(1)
else:
l.append(0)
else:
l.append(random.choice([0,1]))
但是有时0和1的计数是不均匀的。
在不使用拒绝的情况下获得均匀性是棘手的。
拒绝方法很简单,就像
def brute(n):
seq = [0]*n+[1]*n
while True:
random.shuffle(seq)
if not any(len(set(seq[i:i+3])) == 1 for i in range(len(seq)-2)):
break
return seq
在较大的n处将非常慢,但可靠。
在几乎不重要的情况下,采取一种不拒绝样本的方法可能很巧妙,但是我看不到它,而是转而使用通常可以使用的方法。 如果在每个分支点上对选项进行加权(如果您选择了该选项,则根据生成的成功序列数对这些选项进行加权),可以确保您对空间进行了均匀采样。
因此,我们使用动态编程来制作一个实用程序,该实用程序可以计算可能的序列数,并扩展到剩余(#zeroes,#ones)个位的一般情况,然后使用它为绘制提供权重。 (我们实际上可以将其重构为一个函数,但是我认为它们是分开的,即使它引入了一些重复也更加清楚。)
from functools import lru_cache
import random
def take_one(bits_left, last_bits, choice):
# Convenience function to subtract a bit from the bits_left
# bit count and shift the last bits seen.
bits_left = list(bits_left)
bits_left[choice] -= 1
return tuple(bits_left), (last_bits + (choice,))[-2:]
@lru_cache(None)
def count_seq(bits_left, last_bits=()):
if bits_left == (0, 0):
return 1 # hooray, we made a valid sequence!
if min(bits_left) < 0:
return 0 # silly input
if 0 in bits_left and max(bits_left) > 2:
return 0 # short-circuit if we know it won't work
tot = 0
for choice in [0, 1]:
if list(last_bits).count(choice) == 2:
continue # can't have 3 consec.
new_bits_left, new_last_bits = take_one(bits_left, last_bits, choice)
tot += count_seq(new_bits_left, new_last_bits)
return tot
def draw_bits(n):
bits_left = [n, n]
bits_drawn = []
for bit in range(2*n):
weights = []
for choice in [0, 1]:
if bits_drawn[-2:].count(choice) == 2:
weights.append(0) # forbid this case
continue
new_bits_left, new_last_bits = take_one(bits_left, tuple(bits_drawn[-2:]), choice)
weights.append(count_seq(new_bits_left, new_last_bits))
bit_drawn = random.choices([0, 1], weights=weights)[0]
bits_left[bit_drawn] -= 1
bits_drawn.append(bit_drawn)
return bits_drawn
首先,我们可以看到有多少这样的有效序列:
In [1130]: [count_seq((i,i)) for i in range(12)]
Out[1130]: [1, 2, 6, 14, 34, 84, 208, 518, 1296, 3254, 8196, 20700]
OEIS的A177790名为
从(0,0)到(n,n)的路径数,避免了3个或更多连续的东段和3个或更多连续的北段。
如果您认为这正是我们所拥有的,则将0视为东阶,将1视为北阶。
我们的随机抽奖看起来不错:
In [1145]: draw_bits(4)
Out[1145]: [0, 1, 1, 0, 1, 0, 0, 1]
In [1146]: draw_bits(10)
Out[1146]: [0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0]
并且非常统一:
In [1151]: Counter(tuple(draw_bits(4)) for i in range(10**6))
Out[1151]:
Counter({(0, 0, 1, 0, 1, 0, 1, 1): 29219,
(1, 0, 1, 0, 0, 1, 0, 1): 29287,
(1, 1, 0, 0, 1, 0, 1, 0): 29311,
(1, 0, 1, 0, 1, 0, 1, 0): 29371,
(1, 0, 1, 0, 1, 1, 0, 0): 29279,
(0, 1, 0, 1, 0, 0, 1, 1): 29232,
(0, 1, 0, 1, 1, 0, 1, 0): 29824,
(0, 1, 1, 0, 0, 1, 1, 0): 29165,
(0, 1, 1, 0, 1, 0, 0, 1): 29467,
(1, 1, 0, 0, 1, 1, 0, 0): 29454,
(1, 0, 1, 1, 0, 0, 1, 0): 29338,
(0, 0, 1, 1, 0, 0, 1, 1): 29486,
(0, 1, 1, 0, 1, 1, 0, 0): 29592,
(0, 0, 1, 1, 0, 1, 0, 1): 29716,
(1, 1, 0, 1, 0, 0, 1, 0): 29500,
(1, 0, 0, 1, 0, 1, 0, 1): 29396,
(1, 0, 1, 0, 0, 1, 1, 0): 29390,
(0, 1, 1, 0, 0, 1, 0, 1): 29394,
(0, 1, 1, 0, 1, 0, 1, 0): 29213,
(0, 1, 0, 0, 1, 0, 1, 1): 29139,
(0, 1, 0, 1, 0, 1, 1, 0): 29413,
(1, 0, 0, 1, 0, 1, 1, 0): 29502,
(0, 1, 0, 1, 0, 1, 0, 1): 29750,
(0, 1, 0, 0, 1, 1, 0, 1): 29097,
(0, 0, 1, 1, 0, 1, 1, 0): 29377,
(1, 1, 0, 0, 1, 0, 0, 1): 29480,
(1, 1, 0, 1, 0, 1, 0, 0): 29533,
(1, 0, 0, 1, 0, 0, 1, 1): 29500,
(0, 1, 0, 1, 1, 0, 0, 1): 29528,
(1, 0, 1, 0, 1, 0, 0, 1): 29511,
(1, 0, 0, 1, 1, 0, 0, 1): 29599,
(1, 0, 1, 1, 0, 1, 0, 0): 29167,
(1, 0, 0, 1, 1, 0, 1, 0): 29594,
(0, 0, 1, 0, 1, 1, 0, 1): 29176})
覆盖率也是正确的,因为我们可以通过随机采样(有些运气)来恢复A177790的计数:
In [1164]: [len(set(tuple(draw_bits(i)) for _ in range(20000))) for i in range(9)]
Out[1164]: [1, 2, 6, 14, 34, 84, 208, 518, 1296]
这是一个相当有效的解决方案,尽管它没有涵盖完整的解决方案空间,但可以为您提供相当随机的服从约束的输出。
通过确保单个零的数量等于单个1的数量,以及零对的数量等于一的对的数量,我们可以确保零和一的数量相等。 在一个完全随机的输出列表中,我们希望单打的数量大约是对的数量的两倍。 该算法可以做到这一点:每个列表具有每种类型的12个单打和6对。
这些runlengths
长度存储在名为runlengths
的列表中。 在每一轮中,我们对列表进行混洗以获取零的游程长度序列,然后再次对其进行混洗以获取零的游程长度序列。 然后,我们通过在零和一的游程之间交替来填充输出列表。
为了检查列表是否正确,我们使用sum
函数。 如果零和一的数目相等,则列表的总和为24。
from random import seed, shuffle
seed(42)
runlengths = [1] * 12 + [2] * 6
bits = [[0], [1]]
for i in range(10):
shuffle(runlengths)
a = runlengths[:]
shuffle(runlengths)
b = runlengths[:]
shuffle(bits)
out = []
for u, v in zip(a, b):
out.extend(bits[0] * u)
out.extend(bits[1] * v)
print(i, ':', *out, ':', sum(out))
产量
0 : 0 0 1 0 0 1 1 0 1 0 1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 1 0 1 1 0 1 0 1 1 0 0 1 0 0 1 0 1 1 0 1 1 0 1 : 24
1 : 0 1 0 0 1 0 1 1 0 0 1 0 1 0 1 0 0 1 0 1 1 0 0 1 0 1 1 0 1 0 1 1 0 0 1 0 1 0 1 1 0 0 1 0 1 1 0 1 : 24
2 : 0 0 1 1 0 0 1 0 0 1 0 1 0 1 1 0 1 0 1 1 0 0 1 0 1 1 0 1 1 0 1 0 1 0 1 0 1 0 1 0 0 1 0 1 1 0 0 1 : 24
3 : 0 0 1 0 1 1 0 0 1 1 0 1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 1 0 0 1 0 1 1 0 1 1 0 0 1 1 0 1 0 1 0 1 0 1 : 24
4 : 1 1 0 0 1 0 1 0 1 0 1 1 0 1 1 0 1 0 1 1 0 1 0 1 0 0 1 0 1 0 0 1 0 1 0 0 1 1 0 0 1 0 1 1 0 0 1 0 : 24
5 : 1 1 0 1 0 1 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 1 1 0 1 0 1 0 0 1 1 0 0 1 1 0 0 1 0 1 0 1 0 1 0 0 : 24
6 : 1 0 1 0 0 1 0 0 1 0 0 1 1 0 0 1 1 0 1 0 0 1 1 0 1 1 0 1 1 0 1 0 1 0 1 0 1 0 0 1 0 1 1 0 1 0 1 0 : 24
7 : 0 1 0 0 1 1 0 1 0 0 1 0 1 0 1 0 1 0 1 0 1 1 0 1 1 0 1 0 0 1 0 0 1 0 1 1 0 1 1 0 1 1 0 0 1 0 0 1 : 24
8 : 0 0 1 1 0 1 0 1 0 0 1 0 0 1 0 1 0 1 1 0 1 1 0 1 0 1 0 0 1 0 0 1 0 1 1 0 1 0 1 1 0 1 1 0 0 1 0 1 : 24
9 : 1 0 1 1 0 1 1 0 1 1 0 1 0 0 1 0 1 0 1 1 0 0 1 0 0 1 0 0 1 0 1 0 0 1 0 0 1 0 1 0 1 1 0 1 0 1 1 0 : 24
这是一个遵循您的约束的简单代码:
import random
def run():
counts = [24, 24]
last = [random.choice([0, 1]), random.choice([0, 1])]
counts[last[0]] -= 1
counts[last[1]] -= 1
while sum(counts) > 0:
can_pick_ones = sum(last[-2:]) < 2 and counts[1] > 0.33 * (48 - len(last))
can_pick_zeros = sum(last[-2:]) > 0 and counts[0] > 0.33 * (48 - len(last))
if can_pick_ones and can_pick_zeros:
value = random.choice([0, 1])
elif can_pick_ones:
value = 1
elif can_pick_zeros:
value = 0
counts[value] -= 1
last.append(value)
return last
for i in range(4):
r = run()
print(sum(r), r)
产量
24 [1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0]
24 [0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1]
24 [0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1]
24 [1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0]
合理
在每一步while
循环您可以选择1
,选择0
或两者兼而有之。 你可以选择:
1
如果最后两个元件不是一个和的1的计数比1/3剩余时隙的数量较大: sum(last[-2:]) < 2 and counts[1] > 0.33 * (48 - len(last))
0
如果最后两个元素不为0和0的计数大于1/3剩余时隙的数量较大: sum(last[-2:]) > 0 and counts[0] > 0.33 * (48 - len(last))
1
或0
最后两个元素的总和可以是0
, 1
或2
,如果等于0
则意味着最后两个元件分别为0
,所以你只能挑选0
如果sum(last[-2:]) > 0
。 如果等于2
则表示最后两个元素为1,因此,如果sum(last[-2:]) < 2
,则只能选择1。 最后,您需要检查1和0的元素数量是否至少为剩余位置的三分之一,否则,您将不得不创建三个连续的相等元素。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.