【发布时间】:2011-04-05 02:09:59
【问题描述】:
所以这就是交易:我想(例如)生成 4 个伪随机数,当它们相加时等于 40。这怎么可能是 python 中的圆顶?我可以生成一个随机数 1-40,然后生成另一个介于 1 和余数之间的数字,等等,但是第一个数字将有更大的机会“抓住”更多。
【问题讨论】:
所以这就是交易:我想(例如)生成 4 个伪随机数,当它们相加时等于 40。这怎么可能是 python 中的圆顶?我可以生成一个随机数 1-40,然后生成另一个介于 1 和余数之间的数字,等等,但是第一个数字将有更大的机会“抓住”更多。
【问题讨论】:
生成 4 个随机数,计算它们的总和,将每个数除以总和并乘以 40。
如果你想要整数,那么这将需要一点非随机性。
【讨论】:
b = 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] 形式的结果比均匀分布更有可能发生。
在 [1,37] 范围内只有 37^4 = 1,874,161 个四个整数的排列(允许重复)。枚举它们,保存并计算加起来为 40 的排列。 (这将是一个小得多的数字,N)。
在区间 [0, N-1] 中绘制均匀分布的随机整数 K,并返回第 K 个排列。可以很容易地看出,这保证了可能结果空间上的均匀分布,每个序列位置的分布相同。 (我看到的许多答案都会使最终选择的偏差低于前三个!)
【讨论】:
这是标准解决方案。它类似于 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]
解释:(1) 4 元组(a, b, c, d) 的正整数如a + b + c + d == 40 和(2) 三元组(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)]
constrained_sum_sample_pos(4, 10) 的图解,感谢@FM。 (稍作修改。)
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
【讨论】:
low,这可以通过将a - b 替换为a - b + (low-1) 来完成,并补偿n*(low-1) 的增加通过用total - (min-1)*n 替换total 的两个实例来获得新的总和。我还没有找到添加high 阈值的方法。
high 阈值上运气好吗?
使用multinomial 分发
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])(多次运行给出相似的结果)。对我来说看起来不统一
n * p 在你的情况下是 1/3 * 2**16 ~ 21k。 OP 没有要求统一。
以@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]
这个想法仍然是平均划分人口,然后在给定范围内随机向左或向右移动。由于每个值仍然绑定到统一点,我们不必担心它会漂移。
对于我的目的来说已经足够了,但并不完美。例如:第一个数字总是更高,最后一个数字总是更低。
【讨论】:
如果您想要真正的随机性,请使用:
import 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]
【讨论】: