Вот точное (каждая легальная сумма имеет одинаковую вероятность) решение. Он использует перечисление всех допустимых сумм, не в том смысле, что мы проходим каждую сумму, а скорее, учитывая число 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. Мы используем декоратор кеша, чтобы контролировать рекурсию. Этот декоратор запоминает все комбинации параметров, которые мы уже сделали, поэтому, когда мы приходим к одним и тем же параметрам по разным путям рекурсии, нам не нужно снова делать одни и те же вычисления.
Перечисление работает аналогично. Предположим, лексикографический порядок. Как найти n-й раздел? Опять же, разделить на первую корзину. Поскольку мы знаем, что для каждого значения первый бин может принимать общее количество способов заполнения оставшихся бинов, мы можем сформировать накопленную сумму и затем посмотреть, куда вписывается n. Если n лежит между i-й и i + 1-й частичной суммойЕсли первый индекс равен i + low, мы вычитаем i-ую сумму из n и начинаем заново с оставшихся бинов.