您获得的子集在很大程度上取决于您指定的包含或排除元素的标准。如果你有一个函数criterion,它接受一个元素并返回一个布尔值来表示包含在子集中,那么实际的创建过程就变得简单了
from random import randrange
def random_subset(s, criterion=lambda x: randrange(2)):
return set(filter(criterion, s))
filter 创建一个惰性生成器,因此返回子集是存储选择的唯一位置。默认标准非常简单,分布均匀。 randrange 与randint 类似,只是它在右边界是独占的。至少从 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)
这个例子在实践中不是很好,因为集合是无序的,并且可以在同一脚本的不同运行之间给出不同的迭代顺序。然而,它显示的是一种创建随机标准的方法,但会根据子集中已经存在的元素进行调整。
避免使用 Numpy
现在我对您的初衷有了更好的理解,您可以利用 Python 3 具有无限长度整数以及 choices 接受长度参数来获得正确长度的事实。不过我不推荐这种方法:
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