如果您想避免创建(或迭代)满足排列的完整空间(这对于大型 N 很重要),那么您可以使用顺序样本解决此问题.
我的第一种方法是从 [0, N] 中统一绘制一个值,称之为x。然后从[0, N-x]中统一画一个值,取名为y,然后设置z = N - x - y。如果你然后将这三个洗牌,你会从解决方案的空间中得到一个合理的平局,但它不会完全一致。
例如,考虑N=3 的位置。那么 (3, 0, 0) 的某些排列的概率是 1/4,即使它只是 10 个可能的三元组中的一个。所以这个特权值包含一个很高的最大值。
您可以通过根据x 对y 可能的值的数量按比例对第一个值x 进行采样,从而完全抵消这种影响。例如,如果x恰好是N,那么y只有1个兼容值,但如果x为0,则有4个兼容值,即0到3。
换句话说,对于从 0 到 N 的 i,让 Pr(X=x) 成为 (N-x+1)/sum_i(N-i+1)。然后让Pr(Y=y | X=x) 在 [0, N-x] 上一致。
结果为 P(X,Y) = P(Y|X=x) * P(X) = 1/(N-x+1) * [N - x + 1]/sum_i(N- i+1),对于每个候选三元组,它看起来是一致的,1/sum_i(N-i+1)。
请注意,sum(N-i+1 for i in range(0, N+1)) 给出了将 3 个非负整数相加得到 N 的不同方法的数量。我不知道一个很好的证明,如果有人在 cmets 中添加一个,我会很高兴!
这里有一个以这种方式采样的解决方案:
import random
from collections import Counter
def discrete_sample(weights):
u = random.uniform(0, 1)
w_t = 0
for i, w in enumerate(weights):
w_t += w
if u <= w_t:
return i
return len(weights)-1
def get_weights(N):
vals = [(N-i+1.0) for i in range(0, N+1)]
totl = sum(vals)
return [v/totl for v in vals]
def draw_summing_triplet(N):
weights = get_weights(N)
x = discrete_sample(weights)
y = random.randint(0, N-x)
triplet = [x, y, N - x - y]
random.shuffle(triplet)
return tuple(triplet)
感谢 cmets 中的 @DSM 质疑我的原始答案并提供了良好的反馈。
在这种情况下,我们可以这样测试采样器:
foo = Counter(draw_summing_triplet(3) for i in range(10**6))
print foo
Counter({(1, 2, 0): 100381,
(0, 2, 1): 100250,
(1, 1, 1): 100027,
(2, 1, 0): 100011,
(0, 3, 0): 100002,
(3, 0, 0): 99977,
(2, 0, 1): 99972,
(1, 0, 2): 99854,
(0, 0, 3): 99782,
(0, 1, 2): 99744})