繁体   English   中英

python列表的伪随机生成,其中包含具有游程长度和频率约束的二进制值

[英]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))
  • 两者(如果您可以选择10

最后两个元素的总和可以是012 ,如果等于0则意味着最后两个元件分别为0 ,所以你只能挑选0如果sum(last[-2:]) > 0 如果等于2则表示最后两个元素为1,因此,如果sum(last[-2:]) < 2 ,则只能选择1。 最后,您需要检查1和0的元素数量是否至少为剩余位置的三分之一,否则,您将不得不创建三个连续的相等元素。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM