所以这是问题:我想(例如)生成4个伪随机数,相加起来等于40。如何在Python中实现?我可以生成1-40之间的随机数,然后生成另一个介于余数和1之间的数字,等等,但那么第一个数字会有更大的机会“抓住”更多。
这是标准解决方案。它类似于Laurence Gonsalves的答案,但比那个答案有两个优点。
并且
import random
def constrained_sum_sample_pos(n, total):
"""Return a randomly chosen list of n positive integers summing to total.
Each such list is equally likely to occur."""
dividers = sorted(random.sample(range(1, total), n - 1))
return [a - b for a, b in zip(dividers + [total], [0] + dividers)]
示例输出:
>>> constrained_sum_sample_pos(4, 40)
[4, 4, 25, 7]
>>> constrained_sum_sample_pos(4, 40)
[9, 6, 5, 20]
>>> constrained_sum_sample_pos(4, 40)
[11, 2, 15, 12]
>>> constrained_sum_sample_pos(4, 40)
[24, 8, 3, 5]
解释:存在一个一一对应的关系,使得正整数4元组(a, b, c, d)
满足a + b + c + d == 40
,与整数三元组(e, f, g)
,其中0 < e < f < g < 40
相对应。可以使用random.sample
轻松产生后者。该对应关系在一个方向上由(e, f, g) = (a, a + b, a + b + c)
给出,在反向上由(a, b, c, d) = (e, f - e, g - f, 40 - g)
给出。
如果你想要非负整数(即允许0
),而不是正整数,则有一个简单的转换方法:如果(a, b, c, d)
是非负整数,且它们的和为40
,那么(a+1, b+1, c+1, d+1)
是正整数,它们的和为44
,反之亦然。运用这个思路,我们有:
def constrained_sum_sample_nonneg(n, total):
"""Return a randomly chosen list of n nonnegative integers summing to total.
Each such list is equally likely to occur."""
return [x - 1 for x in constrained_sum_sample_pos(n, total + n)]
感谢 @FM 提供的 constrained_sum_sample_pos(4, 10)
的图形说明。以下是稍作编辑后的内容。
0 1 2 3 4 5 6 7 8 9 10 # The universe.
| | # Place fixed dividers at 0, 10.
| | | | | # Add 4 - 1 randomly chosen dividers in [1, 9]
a b c d # Compute the 4 differences: 2 3 4 1
使用多项分布
from numpy.random import multinomial
multinomial(40, [1/4.] * 4)
在这个例子中,每个变量都将分布为二项式分布,其平均值n * p
等于40*1/4=10
。
multinomial(2**16, [1/3] * 3)/2**16
-> array([0.33073425, 0.33273315, 0.33653259])
(多次运行给出类似的结果)。在我看来,它看起来并不均匀。 - kram1032n * p
,在您的情况下为1/3 * 2**16 ~ 21k
。OP并没有要求均匀性。 - Ruggero Turrab = random.randint(2, 38)
a = random.randint(1, b - 1)
c = random.randint(b + 1, 39)
return [a, b - a, c - b, 40 - c]
(我假设您想要整数,因为您说“1-40”,但这很容易推广到浮点数。)
这是它的工作原理:
a = random.randint(1, b-1)
和 c = random.randint(b+1, 39)
来确保在输出列表中不会得到零。此外,这具有稍微特殊的分布:形如 [1, 1, x, 38-x]
的结果出现的概率比均匀分布要高得多。 - Mark Dickinson生成4个随机数,计算它们的和,将每个数除以和再乘以40。
如果你想要整数,那么这需要一些非随机性。
这是对@Mark Dickinson版本的一种谦虚的改进,允许生成的整数包含零(使它们非负,而不是正数):
import random
def constrained_sum_sample_pos(n, total):
"""Return a randomly chosen list of n non-negative integers summing to total.
Each such list is equally likely to occur."""
dividers = sorted(random.choices(range(0, total), k=n-1))
return [a - b for a, b in zip(dividers + [total], [0] + dividers)]
random.choices()
函数进行有放回的抽样,与 random.sample()
不同,后者是无放回的抽样。这个函数从 Python 3.6 开始新增。
n
。例如,当n = 3
和total = 2
时,有六个解:([0, 0, 2], [0, 2, 0], [2, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 0]
)。看起来,这段代码大约一半的时间会给出[0, 1, 1]
,而[0, 0, 2]
和[1, 0, 1]
每个都有四分之一的概率。 - Mark Dickinsonimport numpy as np
def randofsum_unbalanced(s, n):
# Where s = sum (e.g. 40 in your case) and n is the output array length (e.g. 4 in your case)
r = np.random.rand(n)
a = np.array(np.round((r/np.sum(r))*s,0),dtype=int)
while np.sum(a) > s:
a[np.random.choice(n)] -= 1
while np.sum(a) < s:
a[np.random.choice(n)] += 1
return a
def randofsum_balanced(s, n):
return np.random.multinomial(s,np.ones(n)/n,size=1)[0]
在@markdickonson的基础上,通过提供一些控制因子之间分配的方法,我引入了方差/抖动作为每个因子之间均匀距离的百分比。
def constrained_sum_sample(n, total, variance=50):
"""Return a random-ish list of n positive integers summing to total.
variance: int; percentage of the gap between the uniform spacing to vary the result.
"""
divisor = total/n
jiggle = divisor * variance / 100 / 2
dividers = [int((x+1)*divisor + random.random()*jiggle) for x in range(n-1)]
result = [a - b for a, b in zip(dividers + [total], [0] + dividers)]
return result
示例输出:
[12, 8, 10, 10]
[10, 11, 10, 9]
[11, 9, 11, 9]
[11, 9, 12, 8]
这个想法是将人口平均分配,然后在给定范围内随机向左或向右移动它们。由于每个值仍然绑定到统一点,我们不必担心它漂移。
对于我的目的来说已经足够好了,但并不完美。例如:第一个数字总是会变得更高,而最后一个数字总是会变得更低。