生成随机数,使其总和为预定值

44 投票
8 回答
22191 浏览
提问于 2025-04-16 03:25

这里的情况是这样的:我想生成4个伪随机数,这4个数加起来要等于40。请问在Python中怎么做到这一点呢?我可以先生成一个1到40之间的随机数,然后再生成一个1到剩余数之间的随机数,依此类推。但这样的话,第一个生成的数就更有可能“占用”更多的值。

8 个回答

21

使用多项分布

from numpy.random import multinomial
multinomial(40, [1/4.] * 4)

在这个例子中,每个变量的分布就像是一个二项分布,平均值是n * p,也就是40 * 1/4 = 10

122

这是一个标准的解决方案。它和Laurence Gonsalves的回答很相似,但有两个优点。

  1. 它是统一的:每种组合的4个正整数加起来等于40的可能性是一样的。

还有

  1. 它很容易适应其他总和(比如7个数字加起来等于100等等)。
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
13
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”,不过这个方法也可以很容易地适用于小数。)

下面是这个方法的工作原理:

  • 首先,把总范围随机分成两部分,这个分割点叫做b。之所以说是奇数范围,是因为在中间点的两边至少会有2个数。(这是因为你要求每个值的最小值是1。)
  • 接着,再把这两部分各自随机分成两部分。同样,这样做是为了保证每部分的最小值是1。
  • 最后,返回每一部分的大小。所有部分的大小加起来会等于40。

撰写回答