这是一个精确的(每个合法总和都有相同的概率)解决方案。它使用所有合法总和的枚举,不是在我们遍历每个总和的意义上,而是给定一个数字 n,我们可以直接计算枚举中的第 n 个总和。由于我们也知道合法和的总数,我们可以简单地绘制统一整数并对其进行转换:
import numpy as np
import functools as ft
#partition count
@ft.lru_cache(None)
def capped_pc(N,k,m):
if N < 0:
return 0
if k == 0:
return int(N<=m)
return sum(capped_pc(N-i,k-1,m) for i in range(m+1))
capped_pc_v = np.vectorize(capped_pc)
def random_capped_partition(low,high,n,total,size=1):
total = total - n*low
high = high - low
if total > n*high or total < 0:
raise ValueError
idx = np.random.randint(0,capped_pc(total,n-1,high),size)
total = np.broadcast_to(total,(size,1))
out = np.empty((size,n),int)
for j in range(n-1):
freqs = capped_pc_v(total-np.arange(high+1),n-2-j,high)
freqs_ps = np.c_[np.zeros(size,int),freqs.cumsum(axis=1)]
out[:,j] = [f.searchsorted(i,"right") for f,i in zip(freqs_ps[:,1:],idx)]
idx = idx - np.take_along_axis(freqs_ps,out[:,j,None],1).ravel()
total = total - out[:,j,None]
out[:,-1] = total.ravel()
return out + low
演示:
# 4 values between 1 and 5 summing to 12
# a million samples takes a few seconds
x = random_capped_partition(1,5,4,12,1000000)
# sanity checks
# values between 1 and 5
x.min(),x.max()
# (1, 5)
# all legal sums occur
# count them brute force
sum(1 for a in range(1,6) for b in range(1,6) for c in range(1,6) if 7 <= a+b+c <= 11)
# 85
# and count unique samples
len(np.unique(x,axis=0))
# 85
# check for uniformity
np.unique(x, axis=0, return_counts=True)[1]
# array([11884, 11858, 11659, 11544, 11776, 11625, 11813, 11784, 11733,
# 11699, 11820, 11802, 11844, 11807, 11928, 11641, 11701, 12084,
# 11691, 11793, 11857, 11608, 11895, 11839, 11708, 11785, 11764,
# 11736, 11670, 11804, 11821, 11818, 11798, 11587, 11837, 11759,
# 11707, 11759, 11761, 11755, 11663, 11747, 11729, 11758, 11699,
# 11672, 11630, 11789, 11646, 11850, 11670, 11607, 11860, 11772,
# 11716, 11995, 11802, 11865, 11855, 11622, 11679, 11757, 11831,
# 11737, 11629, 11714, 11874, 11793, 11907, 11887, 11568, 11741,
# 11932, 11639, 11716, 12070, 11746, 11787, 11672, 11643, 11798,
# 11709, 11866, 11851, 11753])
简要说明:
我们使用简单的递归来计算上限分区的总数。我们在第一个 bin 上拆分,即我们固定第一个 bin 中的数字,并通过递归检索填充剩余 bin 的方法数量。然后我们简单地总结不同的第一个 bin 选项。我们使用缓存装饰器来控制递归。这个装饰器会记住我们已经完成的所有参数组合,因此当我们通过不同的递归路径到达相同的参数时,我们不必再次进行相同的计算。
枚举的工作方式类似。假设字典顺序。如何找到第 n 个分区?再次,在第一个垃圾箱上拆分。因为我们知道对于每个值,第一个 bin 可以采用剩余 bin 可以填充的方式的总数,我们可以形成累积和,然后查看 n 适合的位置。如果 n 位于第 i 个和第 i+1 个部分和之间第一个索引是 i+low,我们从 n 中减去第 i 个和,然后重新开始剩余的 bin。