【问题标题】:Limit the number of repeats in pseudo random python list限制伪随机python列表中的重复次数
【发布时间】:2015-03-16 15:52:23
【问题描述】:

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

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

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

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

【问题讨论】:

  • 所以你要避免1 连续三次。这就是你想要的
  • 或者连续 3 次 2 次,这可能会发生。
  • 是的,我需要避免连续进行超过 3 次“1”或“2”次测试。
  • 这似乎是一个有趣的问题。简单地从列表中附加一个随机元素但在有太多连续数字时抛出异常的简单算法将不起作用。此外,我认为确保您在满足此属性的所有列表中具有统一的概率分布是很棘手的。您是否要求发行版具有此属性?
  • 如果您要求分布均匀,一种方法是生成所有满足您的约束的列表并从中随机选择一个。这显然是非常低效的,我相信有更好的方法

标签: python random repeat shuffle


【解决方案1】:

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

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[:])

【讨论】:

  • 好点。随着问题的规模越来越大,生成每个可能的解决方案的实用性就越低。就个人而言,如果我采用您建议的方法,我不会只获得 10 次洗牌并希望有些人能够幸存下来。我会将分组测试放在循环中,并且只附加 usable 结果。
  • @JohnY 这就是我想说的,是的。我会编辑它,因为它显然不清楚。
  • 谢谢@Veedrac。我最喜欢这个,因为它在获得有效的解决方案之间具有灵活性,而不必花费“更多”时间来计算所有可能的答案。我说它更灵活,因为如果我想改为说只有 10 个测试或 50 个测试,我可以轻松地调整此方法,而无需重新计算解决方案空间。我可能会增加样本数量以获得更好的随机选择。
【解决方案2】:

这是一个乐观重试策略:

#!/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 名成员的银行需要您明确允许更深层次的递归。

【讨论】:

  • 感谢@Reut Sharabani,这非常聪明。我喜欢你总是得到所需数量的测试,每个 10 个。我也喜欢在整个过程中使用choice()。如果我要使用它,我可能会稍微改变它,因为它不允许 3 次重复。在这一点上,我不确定我是否关心最多 2 次重复。
  • 我错过了您允许 3 次重复的事实。稍后我可能会更改它(我在手机上),虽然这个想法很清楚,并且从为自动机中的另外两个状态添加代码中学到的东西不多。
  • 不过,这不会在可能的解决方案上产生均匀分布。这绝对是块状的。
  • @JohnY 我想知道均匀分布。为什么它不产生这样的分布?
  • 我的概率知识不够强,无法给你严谨的答复。我会说我在 8196 解决方案上针对直接 random.choice 测试了您的方法(最多只能连续运行两个);而直截了当的random.choice 更加随机。我怀疑这与自动机中并非每个随机选择的权重相同这一事实有关。一些选择可以有效地分配两个动作(导致added11added22 的选择是导致added1added2 的选择的两倍“重”)。
【解决方案3】:

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

在您的特定情况下,您始终只运行 20 个测试,并且您只有 2 个可能的测试可供选择。所以你知道你不可能有超过 2**20 个序列,即使没有其他约束。这只是 1048576 种可能性,用今天的内存很容易管理。

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

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

所以,我强烈建议只计算所有可用的可能性,然后在这个可能性集合上使用random.choice

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

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:] 去掉)。

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

【讨论】:

  • 干得好。它适用于这种规模的问题,但如果你在 MCU 上这样做,你就会遇到问题!对于严肃的科学实验,这是最好的解决方案。
  • 是的,谢谢@John Y。这是对这个问题最正确的思考方式。使用这种方法将提供最佳的随机选择
【解决方案4】:

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

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())

【讨论】:

  • 当然,除了它不起作用......(onestwos 过早“用完”项目......是有道理的。)我更喜欢这个与其他答案相比,但需要对其进行更改才能使其正常工作,不确定如何或即使可能。
  • 感谢您的尝试,任何认为值得尝试回答的人都让我受宠若惊。不幸的是,这里的逻辑不太正确,我仍然可以连续获得超过 3 个“1”或“2”测试。如果您循环 14 次而不是 20 次,则循环的工作频率会更高。
【解决方案5】:

这并不完全保留 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:]

【讨论】:

  • 这真的很棒,我将“randomList[-2:]+”部分更改为“randomList[-3:]+”,因为可以重复3次,只是不超过3 次重复。但是,正如您所说,它不能保证我每次测试都会有 10 个。不过我会记住这一点。我只是感谢您的帮助!
  • 是的,抱歉,我注意到当我试图让它准确地选择每个 10 时,忘记更新问题,因为我冻结了 python 3 次并放弃了哈哈。
【解决方案6】:

这是 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

【讨论】:

    猜你喜欢
    • 2014-03-09
    • 1970-01-01
    • 2021-06-06
    • 2014-06-12
    • 2016-10-28
    • 2017-03-28
    • 1970-01-01
    • 2012-10-11
    • 1970-01-01
    相关资源
    最近更新 更多