关于java:越来越不可能获得数字越大的随机数

Get random number with larger numbers increasingly unlikely

如何获得k到h范围内的随机数,使得数字越接近h,它就越不可能出现?

我需要20到1980年之间的数字。


我在Eclipse中尝试过一些东西,这里有结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Generator {      
    double generate(double low, double high);
}


abstract class AbstractGenerator implements Generator {        
    protected final Random rand;

    public AbstractGenerator()
    {
        rand = new Random();
    }

    public AbstractGenerator(long seed)
    {
        rand = new Random(seed);
    }
}

现在结果是各种生成器实现:

我试图在0到9的等级上生成100k数字,这里它们显示为条形。

Catan 2(加两个骰子)

1
2
3
4
5
6
7
8
class Catan2 extends AbstractGenerator {

    @Override
    public double generate(double low, double high)
    {
        return low + (high - low) * Math.abs(-1 + (rand.nextDouble() + rand.nextDouble()));
    }
}

Reusults:

1
2
3
4
5
6
7
8
9
10
0 : *******************
1 : ******************
2 : ****************
3 : **************
4 : ************
5 : *********
6 : *******
7 : *****
8 : ***
9 : *

Catan 3(加3个骰子)

1
2
3
4
5
6
7
8
class Catan3 extends AbstractGenerator {

    @Override
    public double generate(double low, double high)
    {
        return low + (high - low) * Math.abs(-1.5 + (rand.nextDouble() + rand.nextDouble() + rand.nextDouble())) / 1.5;
    }
}

Reusults:

1
2
3
4
5
6
7
8
9
10
0 : ***********************
1 : *********************
2 : *******************
3 : ***************
4 : ***********
5 : *******
6 : *****
7 : ***
8 : *
9 : *

Catan 4(加4个骰子)

1
2
3
4
5
6
7
8
class Catan4 extends AbstractGenerator {

    @Override
    public double generate(double low, double high)
    {
        return low + (high - low) * Math.abs(-2 + (rand.nextDouble() + rand.nextDouble() + rand.nextDouble() + rand.nextDouble())) / 2D;
    }
}

结果:

1
2
3
4
5
6
7
8
9
10
0 : ***************************
1 : ************************
2 : ********************
3 : **************
4 : *********
5 : *****
6 : ***
7 : *
8 : *
9 : *

我认为"Catan 3"是最好的。

公式为:low+(high-low)*abs(-1.5+(RAND+RAND+RAND))/1.5

基本上,我得到一个"山"分布,然后我居中并取其绝对价值。然后我将其标准化为所需的值。


而另一种选择。 有一些标准方法可以在高斯分布上产生随机数。 设置高斯RNG,平均值为k,标准差为h / 5。 拒绝任何低于k的数字(约为生成数字的一半)并拒绝所有大于h的数字(5%或更少)。

如果要优化结果,可以调整标准偏差。 实际上,这是具有截尾的半高斯RNG,因此数字不是线性的; 你会更接近k而不是h。

ETA:感谢@ MightyPork的评论,让我思考。 高斯分布是对称的,因此不需要丢弃任何小于k的原始值。 只需将它们从k下方移到k以上的相同距离:

1
2
3
if (raw < k)
  raw <- k + (k - raw)
end if

高于h的值仍然需要被拒绝。


假设我们的范围是[0,4],创建一个这样的数组:

1
[000001111222334]

现在使用标准Random对象从数组中绘制。通过这样做,我们已经从均匀分布的绘制转变为我们自己设计的分布。实际上,我们不打算使用辅助阵列。您可以执行以下操作来代替辅助数组:

从[0,14]得出;将[0,4]映射到0,[5,8]到1,[9,11]到2,[12,13]到3和[14]到4。

这实际上取决于您的发行版。您可以通过在不同范围内从均匀分布中多次绘制来从非均匀分布中近似绘制。当然,如果您知道分布的概率质量函数或概率密度函数,那么您就是金色的。


如果你需要很好地控制数字的分布,那么一个好的方法就是反转方法。创建一个(x,y)对的排序表,其中x和y都单调增加:x从0到1,y从你需要的伪随机数的低到高值。算法是:

1
2
3
4
x = uniform random float in [0..1)
Search the table to find (x[i],y[i]) such that x[i] <= x < x[i+1]
// Return linearly interpolated y value
return y[i] + (x - x[i]) / (x[i+1] - x[i]) * (y[i+1] - y[i])

您可以使用表条目控制返回值的分布。

如果表只包含(0,0)和(1,1),那么显然返回值等于x,并且分布是均匀的。要获得更高的数字,请描述在开始时增加得更快的曲线,并在更高的x值处更平坦,例如:

1
(0,0) (0.25,0.5) (1,1)

你应该能够看出为什么这样有效。在均匀分布中,一半的数字在0到0.5之间。使用此表,只有四分之一的数字在该范围内,因此其他四分之三的数字在0.5到1之间。根据需要,高数字更频繁。

只要单调增加,您就可以创建任意曲线和任何形状的曲线。如果表有多对,请考虑二进制搜索速度。

对于20到1980的范围,相应的表将类似于:

1
(0, 20) (0.25, 1000) (1, 1980)

如果你需要整数,你想要使用

1
(0, 20) (0.25, 1000) (1, 1981)

然后截断结果中的分数。

同样,您可能希望表中有更多的点来使ICDF更加平滑。这是为了说明。

数学

存储在表中的曲线称为返回的伪随机数的反向累积密度函数(ICDF)。概率分布函数(PDF)是非负函数,其曲线下面积为1.常用的PFD是均匀的,指数的和正态的。相应的CDF是PDF的运行积分。 ICDF与CDF相反。众所周知,要使用任何给定的PDF生成随机数,您可以找到ICDF并应用上述算法。