在具有常数和的范围内生成N个随机数

时间:2015-03-21 19:26:40

标签: c++ algorithm random sum range

我想生成从[a,b]之间的特定分布(例如均匀随机)中抽取的N个随机数,它们总和为常数C.我尝试了几种我自己能想到的解决方案,并且有些建议在类似的线程,但大多数都是为有限的问题工作或我无法证明结果仍然遵循所需的分布。

我尝试过: Generage N随机数,将它们全部除以它们的总和并乘以所需的常数。这似乎有效,但结果并不遵循数字应该在[a:b]内的规则。

Generage N-1随机数加0和期望的常数C并对它们进行排序。然后计算每两个连续nubmers之间的差异,结果是差异。这再次总结为C但是具有与最后一个方法相同的问题(范围可以大于[a:b]。

我还尝试生成随机数,并始终以保持所需总和和范围的方式跟踪最小值和最大值,并提供此代码:

bool generate(function<int(int,int)> randomGenerator,int min,int max,int len,int sum,std::vector<int> &output){
    /**
    * Not possible to produce such a sequence
    */
if(min*len > sum)
    return false;
if(max*len < sum)
    return false;

int curSum = 0;
int left = sum - curSum;
int leftIndexes = len-1;
int curMax = left - leftIndexes*min;
int curMin = left - leftIndexes*max;

for(int i=0;i<len;i++){
    int num = randomGenerator((curMin< min)?min:curMin,(curMax>max)?max:curMax);
    output.push_back(num);
    curSum += num;
    left = sum - curSum;
    leftIndexes--;
    curMax = left - leftIndexes*min;
    curMin = left - leftIndexes*max;
}

return true;
}

这似乎有效但结果有时非常偏斜,我不认为它遵循原始分布(例如统一)。 E.g:

//10 numbers within [1:10] which sum to 50:
generate(uniform,1,10,10,50,output);
//result:
2,7,2,5,2,10,5,8,4,5 => sum=50
//This looks reasonable for uniform, but let's change to 
//10 numbers within [1:25] which sum to 50:
generate(uniform,1,25,10,50,output);
//result:
24,12,6,2,1,1,1,1,1,1 => sum= 50

注意输出中存在多少个。这可能听起来合理,因为范围更大。但它们看起来并不像一个统一的分布。 我不确定即使有可能实现我想要的,也许限制使问题无法解决。

5 个答案:

答案 0 :(得分:14)

如果您希望样本遵循均匀分布,则问题会减少,生成N个随机数,其中sum = 1.这反过来是Dirichlet分布的特例,但也可以使用指数分布。方法如下:

  1. 统一样本v 1 ... v N ,所有v i 介于0和1之间。
  2. 对于所有i,1&lt; = i&lt; = N,定义u i := -ln v i (注意你 i &gt; 0)。
  3. 将u i 归一化为p i := u i / s其中s是总和u 1 + ... + U ñ
  4. p 1 .. p N 均匀分布(在dim N-1的单形中),它们的和为1。

    现在你可以将这个p i 乘以你想要的常数C,并通过将其他常数A相加来翻译它们

    q i := A + p i * C.

    编辑3

    为了解决评论中提出的一些问题,请允许我添加以下内容:

    • 为了确保最终的随机序列落在区间[a,b]中,选择上面的常数A和C为A:= a和C:= ba,即取q i = a + p i *(ba)。由于p i 在范围(0,1)内,所以q i 将在[a,b]范围内。
    • 如果v i 碰巧为0,则不能取(负)对数-ln(v i ),因为ln()未定义为0。这样的事件非常少。但是,为了确保不发出错误信号,上面第1项中的v 1 ... v N 的生成必须以特殊方式威胁任何出现的0:将-ln(0)视为+无穷大(记住:当x-> 0时,ln(x) - &gt; -infinity)。因此,和s = +无穷大,这意味着p i = 1而所有其他p j = 0.没有这种约定,序列(0 ... 1 .. .0)永远不会产生(很多感谢@Severin Pappadeux这个有趣的评论。)
    • 正如@Neil Slater在问题附带的第4条评论中所解释的那样,逻辑上不可能满足原始框架的所有要求。因此,任何解决方案都必须将约束放宽到原始约束的适当子集。 @Behrooz的其他评论似乎证实在这种情况下这就足够了。

    编辑2

    评论中又提出了一个问题:

    为什么重新定标统一样本是不够的?

    换句话说,我为什么要费心去采取负对数?

    原因是,如果我们只是重新缩放,那么得到的样本将不会均匀地分布在片段(0,1)上(或[a,b]用于最终样本。)

    为了想象这个让我们想到2D,即让我们考虑N = 2的情况。均匀样本(v 1 ,v 2 )对应于原点(0,0)和角(1,1)的正方形中的随机点。现在,当我们将这个点除以s和v 1 + v 2 进行归一化时,我们正在做的是将点投影到对角线上,如图所示(请记住,对角线是x + y = 1行):

    enter image description here

    但是,假设从(0,0)到(1,1)更接近主对角线的绿线比橙色的更长,更靠近轴x和y,则投影趋于累积更多地围绕投影线的中心(蓝色),缩放的样本存在于其中。这表明简单的缩放不会在所描绘的对角线上产生均匀的样本。另一方面,可以在数学上证明负对数确实产生所需的均匀性。因此,我将邀请每个人实施这两种算法,并检查结果图的行为与此答案所描述的不同,而不是复制数学证明。

    注意: here是关于这个有趣主题的博客文章,适用于石油和天然气行业)

答案 1 :(得分:4)

让我们尝试简化问题。 通过减去下限,我们可以将其减少为在 [0,ba] 中找到 N 数字,使得它们的总和 C-Na

重命名参数,我们可以在 [0,m] 中寻找 N 数字,其总和 S

现在问题类似于在长度 [0,m] N 不同子段中划分长度 S 的段。

我认为问题根本无法解决。

如果S = 1,N = 1000且m大于0,则唯一可能的重新分配是1和999个零,这与随机传播完全不同。

N m S 之间存在相关性,即使选择随机值也不会使其消失。

对于最均匀的重新分区,子段的长度将遵循高斯曲线,平均值 S / N

如果你以不同的方式调整你的随机数,你最终会得到任何偏见,但最终你将永远不会有统一的[a,b]重新分配和C的总长度,除非你的长度[a] ,b]间隔恰好是2C / Na。

答案 2 :(得分:1)

对于我的回答,我假设我们有统一的分布。

由于我们有统一的分布,C的每个元组都有相同的概率发生。例如,对于a = 2, b = 2, C = 12, N = 5,我们有15个可能的元组。从他们10开始243开头,14开头。这样可以选择从115的随机数,以便选择第一个元素。从110,我们选择2,从1114我们选择3,而15我们选择4 1}}。然后我们继续递归。

#include <time.h>
#include <random>

std::default_random_engine generator(time(0));
int a = 2, b = 4, n = 5, c = 12, numbers[5];

// Calculate how many combinations of n numbers have sum c
int calc_combinations(int n, int c) {
    if (n == 1) return (c >= a) && (c <= b);
    int sum = 0;
    for (int i = a; i <= b; i++) sum += calc_combinations(n - 1, c - i);
    return sum;
}

// Chooses a random array of n elements having sum c
void choose(int n, int c, int *numbers) {
    if (n == 1) { numbers[0] = c; return; }

    int combinations = calc_combinations(n, c);
    std::uniform_int_distribution<int> distribution(0, combinations - 1);
    int s = distribution(generator);
    int sum = 0;
    for (int i = a; i <= b; i++) {
        if ((sum += calc_combinations(n - 1, c - i)) > s) {
            numbers[0] = i;
            choose(n - 1, c - i, numbers + 1);
            return;
        }
    }
}

int main() { choose(n, c, numbers); }

可能的结果:

2
2
3
2
3

由于组合计算中的溢出(除非我们使用大整数库),此计算所需的时间以及任意大的随机需求,此算法不能很好地扩展到大N号。

答案 3 :(得分:0)

嗯,对于n = 10000,我们在那里有一个不随机的小数字?

可能会生成序列,直到达到sum > C-max,然后只需输入一个简单的数字即可。

10000中的1更像是系统中的一个非常小的噪音。

答案 4 :(得分:0)

虽然这是一个老话题,但我想我有个主意。考虑我们想要N个随机数,其中和为C,并且a和b之间的每个随机数。为了解决问题,我们创造N个洞并准备C球,每次我们问每个洞“你想要另一个球吗?”。如果不是,我们将进入下一洞,否则,我们将球放入洞中。每个孔都有一个上限值:b-a。如果某个孔达到上限值,则总是传递到下一个孔。

例:
0和2之间的3个随机数,其和为5。

模拟结果:
第一轮: - + -
第二轮:++ -
第三轮:--- 第四轮:+ * +
决赛:221

- :拒绝球
+:接受球
*:全程