限制伪随机python列表中的重复次数

Limit the number of repeats in pseudo random python list

对于科学实验,我需要生成一个伪随机顺序来管理两个不同的测试,每个测试 10 次。我用过这段代码:

import random
randy  = [1] * 10 + [2] * 10 
random.shuffle(randy)

这给了我一个很好的随机测试顺序,但是我需要确保重复测试的最大次数不大于 3。换句话说,不要执行“1”测试超过 3 次连续。

谁能想到一个好的方法来做到这一点?多次洗牌并不能保证成功。我有什么办法可以有力地检查洗牌列表并相应地更改它?谢谢!

我会制作您自己的洗牌器,因为它可能是最快、最优雅的选择:

randy = []

ones = [1] * 10
twos = [2] * 10

for i in range(20):
    if len(randy) > 3 and randy[i-1] == randy[i-2] == randy[i-3]:
        randy.append(ones.pop() if randy[i-1] == 1 else twos.pop())
    else:
        randy.append(random.choice([ones, twos]).pop())

这是一个乐观重试策略:

#!/usr/bin/env python
from random import choice

def added1(lst, bank):
    if len(bank) == 0:
        return lst
    selection = choice(bank)
    lst.append(selection)
    bank.remove(selection)
    if selection == 1:
        return added11(lst, bank)
    return added2(lst, bank)

def added11(lst,bank):
    if len(bank) == 0:
        return lst
    bank.remove(2)
    lst.append(2)
    return added2(lst, bank)

def added2(lst, bank):
    if len(bank) == 0:
        return lst
    selection = choice(bank)
    lst.append(selection)
    bank.remove(selection)
    if selection == 2:
        return added22(lst, bank)
    return added1(lst, bank)

def added22(lst,bank):
    if len(bank) == 0:
        return lst
    bank.remove(1)
    lst.append(1)
    return added1(lst, bank)

def start(lst, bank):
    bank_bkp = bank[:]
    while True:
        try:
            if len(bank) == 0:
                return lst
            selection = choice(bank)
            lst.append(selection)
            bank.remove(selection)
            if selection == 1:
                return added1(lst, bank)
            return added2(lst, bank)
        except:
            # retry
            bank = bank_bkp[:]
            lst = []


print start([], [1] * 10 + [2] * 10)

输出:

[1, 1, 2, 1, 1, 2, 2, 1, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 2, 2]

它基于表示此自动机中状态的简单函数:

执行规则和一系列选项。如果选项库用完 - 它会再次尝试。

可能可能会花费很多时间,但不会:

print timeit.repeat('start([], [1] * 10 + [2] * 10)', setup="from __main__ import start", number=10000, repeat=3)

输出:

[0.14524006843566895, 0.14585399627685547, 0.14375996589660645]

注意:这是递归的,因此拥有超过 2000 名成员的银行要求您明确允许更深层次的递归。

这并没有准确地保留 10 个 1 和 10 个 2,因此可能不是您想要的,但它基于机会(目前每个概率为 50%),并且您可以根据需要添加新测试。

import random
from operator import itemgetter

#randy = [ [item,amount], ... ]
randy  = [[1,10],[2,10]]

#This turns the above list into the same format of your 'randy'
itemList = [j for k in[([i[0]]*i[1])for i in randy]for j in k]  

randomList = [-1]  #This stops the check from causing problems at the start
for i in range(len(itemList)):
    while True:
        newChoice = random.choice( itemList )
        if len(set(randomList[-2:]+[newChoice]))-1: #Checks the last 2 values plus the new value aren't all the same
            randomList.append( newChoice )
            break
shuffledList = randomList[1:]

对于这么小的问题,我不同意@texasflood 的评论,即预先计算所有可能性,然后仅从中挑选将是非常低效的。事实上,规定的参数非常小,仅使用蛮力生成所有可能性是非常容易管理的,正如我将在下面演示的那样。

在您的特定情况下,您总是只有 运行 20 个测试,并且您只有 2 个可能的测试可供选择。所以你知道你不可能有超过 2**20 个序列,即使没有其他限制。这只有1048576种可能,以今天的记忆力轻松应对。

此外,根据您的问题陈述,您只能使用一个测试中的 10 个和另一个测试中的 10 个。这将可能性的数量减少到 184756。(使用经典的概率计数技术,这被计算为 20!/(10!*10!)。)

那是在您甚至还没有排除连续 运行 四个(或更多)相同测试的可能性之前。

因此,我强烈建议只计算所有可用的可能性,然后对这些可能性集合使用 random.choice

为了帮助您入门,这里有一个简单的循环,用于获取恰好包含 10 个零和 10 个一的所有可能序列:

sequences = []
for n in range(2**20):
    b = bin(n)[2:].zfill(20)
    if b.count('1') == 10:
        sequences.append(b)

请注意,bin 函数(需要 Python 2.6 或更高版本)生成整数的二进制字符串表示形式,以 '0b' 开头(因此 [2:] 将其剥离)。

我将把它留作 reader 的练习,以消除四行序列。 (提示:您可以通过测试二进制字符串中是否存在 '1111''0000' 来简单地改进我上面给出的示例代码。您将得到总共 66486 个可用序列,以今天的标准来看,这是一个相当小的数字。)

John Y 的解决方案让您搜索整个解决方案space;尽管这是可以忍受的,但几乎不值得这样做。相反,只是乐观地采样:

import random

sequences = []
order = [1, 0] * 10

while len(sequences) < 10:
    random.shuffle(order)

    if order in sequences:
        continue

    sequences.append(order[:])

然后要删除长度为 4 的组,您可以检查类似

from itertools import groupby

while len(sequences) < 10:
    random.shuffle(order)

    if order in sequences:
        continue

    if all(len(list(group)) < 4 for _, group in groupby(order)):
        sequences.append(order[:])

这是 Veedrac 答案的优化版本,您只想获得一个正确的列表。如果你想动态得到一个序列会更有趣,但如果你想避免序列重复

from random import shuffle
from itertools import groupby

def get_binary_sequence(sequence_length, maximum_repetitions):
    order = [True, False] * int(sequence_length/2)
    while True:
        shuffle(order)
        if all(len(list(group)) <= maximum_repetitions _, group in groupby(order)):
            return order