你的问题等价于下面的问题:
- 在
[1, 20]中选择5个随机整数,总和为20,其中整数以随机顺序出现。
- 将所选整数除以 20。
下面的 Python 代码显示了如何实现这一点。它具有以下优点:
- 它不使用拒绝抽样。
- 从所有符合要求的组合中均匀随机选择。
它基于 John McClane 的算法,他将其发布为 answer to another question。我在另一个答案中describe the algorithm。
import random # Or secrets
def _getSolTable(n, mn, mx, sum):
t = [[0 for i in range(sum + 1)] for j in range(n + 1)]
t[0][0] = 1
for i in range(1, n + 1):
for j in range(0, sum + 1):
jm = max(j - (mx - mn), 0)
v = 0
for k in range(jm, j + 1):
v += t[i - 1][k]
t[i][j] = v
return t
def intsInRangeWithSum(numSamples, numPerSample, mn, mx, sum):
""" Generates one or more combinations of
'numPerSample' numbers each, where each
combination's numbers sum to 'sum' and are listed
in any order, and each
number is in the interval '[mn, mx]'.
The combinations are chosen uniformly at random.
'mn', 'mx', and
'sum' may not be negative. Returns an empty
list if 'numSamples' is zero.
The algorithm is thanks to a _Stack Overflow_
answer (`questions/61393463`) by John McClane.
Raises an error if there is no solution for the given
parameters. """
adjsum = sum - numPerSample * mn
# Min, max, sum negative
if mn < 0 or mx < 0 or sum < 0:
raise ValueError
# No solution
if numPerSample * mx < sum:
raise ValueError
if numPerSample * mn > sum:
raise ValueError
if numSamples == 0:
return []
# One solution
if numPerSample * mx == sum:
return [[mx for i in range(numPerSample)] for i in range(numSamples)]
if numPerSample * mn == sum:
return [[mn for i in range(numPerSample)] for i in range(numSamples)]
samples = [None for i in range(numSamples)]
table = _getSolTable(numPerSample, mn, mx, adjsum)
for sample in range(numSamples):
s = adjsum
ret = [0 for i in range(numPerSample)]
for ib in range(numPerSample):
i = numPerSample - 1 - ib
# Or secrets.randbelow(table[i + 1][s])
v = random.randint(0, table[i + 1][s] - 1)
r = mn
v -= table[i][s]
while v >= 0:
s -= 1
r += 1
v -= table[i][s]
ret[i] = r
samples[sample] = ret
return samples
例子:
weights=intsInRangeWithSum(
# One sample
1,
# Count of numbers per sample
5,
# Range of the random numbers
1, 20,
# Sum of the numbers
20)
# Divide by 100 to get weights that sum to 1
weights=[x/20.0 for x in weights[0]]
无论如何,您的代码如下:
array = np.random.random(5)
array /= np.sum(array)
...不会产生总和为 1 的数字的统一随机组合。相反,请使用以下代码代替该代码,注意统一到达是 指数 间隔的,而不是均匀间隔的. (需要明确的是,这并不能解决您问题中的问题;这只是一个观察结果。不要介意自 NumPy 1.17 以来 numpy.random.* 函数是 now legacy functions 的事实,部分原因是它们使用全局状态。)
array = np.random.exponential(5)
array /= np.sum(array)