将数字拆分为随机的非零数字

时间:2015-10-07 18:42:47

标签: algorithm math

我有一些数字,让我们说

[X1,Y1], [X2,Y2] ..... [Xn,Yn]    Y > X

我有另一个号码,比如Z.现在我想要的是将Z分成随机值Nm,使其始终满足以下条件:

N1 + N2 + N3 ...... Nn = Z
Nm > 0
Nm <=  Ym - Xm

2 个答案:

答案 0 :(得分:0)

IF the series y_1 - x_1 + … + y_m - x_m < z OR x_i ≥ y_i for any i
  FAIL
ELSE DO
  residual := z
  FOR i := 1 to m-1
    n_i := (y_i - x_i) × random (0,1]
    residual := residual - n_i
    IF residual ≤ 0
      BREAK
  n_m := residual 
WHILE n_m ≤ 0 OR n_m > y_m-x_m 

n_m之前的每个n_i都落入范围内,因为它是0到1之间的随机数的上限。最后一个值属于范围,因为如果没有,我们将循环并再次尝试。数字加起来为z,因为n_m是z与系列其余部分之和的差值。

答案 1 :(得分:0)

你有M个区间[Xi,Yi],你想在每个区间有一个随机数,这样所有数字的总和就是Z.

这些数字的总和必须在SUM(Xi)和SUM(Yi)之间。所以我们计算两者:

Zmin = 0;
Zmax = 0;
for (i = 0; i < m; i++) {
    Zmin += X[i];
    Zmax += Y[i];
    Z[i] = X[i];
}

现在我们检查Z是否在Zmin和Zmax之间。如果不是,则无法获得满足约束条件的Z向量。

如果Z介于Zmin和Zmax之间,我们可以设想从Z向量开始,它是X的副本,并在所有Zi中随机地将Z和Zmin之间的差异洒在上面。

remainder = Z - Zmin;
if ((remainder < 0) || (remainder + Zmin > Zmax)) {
    // Can't do.
}

// There are several ways of equidistributing the remainder.
// This is not one of them, but it's simple.
// If Z is very near Zmax, performances will suffer.
while (remainder > 0) {
    // Choose 
    i = rand()*m;
    // Can we increase Z[i]?
    if (Z[i] < Y[i]) {
        Z[i]++;
        remainder--;
    }
}
// DONE!

在O(n)中运行的更复杂的方法是将m个槽中的余数R与每个槽的宽度成比例分配。平均而言,槽(Y [i] -X [i])宽应该接收额外的R *(Y [i] -X [i])/(Zmax-Zmin)。我们可以使用Bresenham的算法尽可能地做到这一点,但如果我们想要一个随机分布,我们就冒着分配前K个槽的风险,并且有一个很大的余数,即使我们最大化了所有的剩下的插槽我们仍然不会用尽它。

为了避免这种情况,我们需要保持一个运行总数,我们可以推迟到以下间隔,从Zmax - Zmin开始。因此,在迭代i中,如果从i + 1到m的下一个间隔可以调度到D,并且余数是​​R,那么我们添加到Z [i]的任何东西都必须离开(余数 - Z [i] + X [i]&lt; = D),这意味着Z [i] -X [i]> =余数-D;即,我们添加到Z [i]的随机数(当前仍然等于X [i])必须从MAX开始(余数-D,0)。理想情况下,这将始终为0.因此,我们可以将MAX(余数-D,0)中的随机数归因于Y [i] -X [i]。设这个间隔宽度为Q.我们希望平均分配Q*remainder/(D+Q)到这个间隔,这意味着Z[i] = Y[i]-X[i]-Q+rand()*(Q*remainder/(D+Q))

D = Zmax - Zmin;

for (i = 0; i < m; i++) {

    V = MAX(remainder-D, 0);

    Q = (Y[i] - X[i]) - V; // This is positive, or we would have failed earlier

    Z[i] = Y[i]-X[i]-Q+rand()*(Q*remainder/(D+Q));

    remainder -= (Z[i] - X[i]);
}

这是PHP中的快速实现,似乎有效。

<?php
    $X = [ 1, 4, 11, 3, 5, 17, 22, 35, 120, 0 ];
    $Y = [ 8, 9, 33, 9, 9, 28, 24, 36, 215, 3 ];

    $m = count($X);

    $ZZ= 0;
    // Construct a Z that will work.
    for ($i = 0; $i < $m; $i++) {
        // $Y[$i] = rand($X[$i], $X[$i] + 20);
        $ZZ += rand($X[$i], $Y[$i]);
    }

    $Zmin = 0;
    $Zmax = 0;

    for ($i = 0; $i < $m; $i++) {
        $Zmin += $X[$i];
        $Zmax += $Y[$i];
    }

    $R = $ZZ - $Zmin;
    if (($R < 0) || ($R + $Zmin > $Zmax)) {
        die("Can't do.\n");
    }

    $D = $Zmax - $Zmin;

   for ($i = 0; $i < $m; $i++) {
        $A = max($R-$D, 0); // Cannot distribute less than this.
        $B = min($Y[$i]-$X[$i], $R); // Nor more than this.


        $Q = ($B - $A); // This is positive, or we would have failed earlier
        assert('$Q > 0');

        $Z[$i] = $X[$i] + rand($A, $B); // floor($A + ($B-$A)*rand());

        $R -= ($Z[$i] - $X[$i]);
        assert('$R >= 0');
        assert('$X[$i] <= $Z[$i]');
        assert('$Y[$i] >= $Z[$i]');
   }