Python:如何获取随机子集

时间:2019-02-19 03:33:34

标签: python python-3.x set subset

如何在python中获得集合s的随机子集?我尝试做

from random import sample, randint

def random_subset(s):
    length = randint(0, len(s))
    return set(sample(s, length))

但是我现在意识到,这显然是行不通的,因为len(s)s0的分布,其中n是随机子集。

我确定我可以计算出这种分布并以概率或类似的方式使用numpy的样本,但是我更希望使用纯python进行一些研究。

2 个答案:

答案 0 :(得分:5)

我刚刚意识到我可以简单地遍历s中的每个元素并独立决定是否保留它。像这样

from random import randint

def random_subset(s):
    out = set()
    for el in s:                                                                                                                    
        # random coin flip
        if randint(0, 1) == 0:
            out.add(el)
    return out

这具有正确的分布。

答案 1 :(得分:0)

您获得的子集将在很大程度上取决于您为包含或排除元素指定的条件。如果您有一个criterion函数,它接受一个元素并返回一个布尔值以指示包含在子集中,那么实际的创建过程将变得简单

from random import randrange

def random_subset(s, criterion=lambda x: randrange(2)):
    return set(filter(criterion, s))

filter创建一个惰性生成器,因此返回子集是唯一存储选择的位置。默认条件非常简单,并且分布均匀。 randrangerandint相似,不同之处在于它在右边界是排他的。至少从Python 3.2+开始,这两个函数都会产生相当均匀的结果,而与范围大小无关。

您可以使用random进一步完善标准:

from random import random

criterion = lambda x: random() < 0.5

应用这样的阈值似乎有点过头,但是它可以让您调整分布。您可以使用一个函数来为您喜欢的阈值生成条件:

def make_criterion(threshold=0.5):
    return lambda x: random() < threshold

您可以使用它来获得较小的子集:

random_subset(s, make_criterion(0.1))

实际上,您可以根据需要使标准复杂。下面的示例是一个人为设计的可调用类,它对字符串集进行操作。如果已经添加了具有匹配的第一个字符的字符串,它将自动拒绝当前元素。如果已经看到第二个字母,它将包含的可能性设置为0.25。否则,它将掷硬币:

class WeirdCriterion:

    def __init__(self):
        self.first = set()
        self.second = set()

    def __call__(self, x):
        n = len(x)
        if n > 0:
            if x[0] in self.first:
                return False
            self.first.add(x[0])
            if n > 1:
                if x[1] in self.second:
                    return not randrange(4)
                self.second.add(x[1])
        return randrange(2)

该示例在实践中不是很好,因为集合是无序的,并且可以在同一脚本的不同运行之间给出不同的迭代顺序。但是,它显示的是一种用于创建随机准则的方法,该准则可根据子集中已有的元素进行调整。

避免脾气暴躁

现在,我对您的原始意图有了更好的了解,您可以利用以下事实:Python 3具有无限长的整数,并且choices接受length参数来获取正确的长度。但我不推荐这种方法:

from random import choices, sample
from math import factorial

def random_subset(s):
    n = len(s)
    nf = factorial(n)
    # yes, there are better ways of doing this, even in pure python
    weights = [nf / (factorial(k) * factorial(n - k)) for k in range(n + 1)]
    length = choices(range(n + 1), weights, k=1)[0]
    return sample(s, length)

计算二项式系数的更好解决方案可能是:

def pascal(n):
    result = [1] * (n + 1)
    if n < 2:
        return result
    for i in range(2, n + 1):
        for j in range(i - 1, 0, -1):
            result[j] += result[j - 1]
    return result