限制伪随机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
对于科学实验,我需要生成一个伪随机顺序来管理两个不同的测试,每个测试 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