将"average"参数添加到.NET的Random.Next()以获得曲线结果

Cyr*_*ral 21 .net c# random algorithm

我希望能够添加" 平均 "参数Random.Next(Lower, Upper).这个方法会有min,maxaverage参数.我创建了一个像这样的方法一段时间用于测试(它使用列表并且非常糟糕),所以我想了解如何编写正确的实现.

拥有此功能的原因是我游戏中的许多程序/随机事件.假设你希望树木大部分时间都是10个单位高,但仍然可以低到5或15.法线Random.Next(5,15)将全部返回结果,但这种方法会有更多的钟形曲线朝向它的结果.意义10将是最常见的,并且在每个方向上出去都不太常见.例如,将平均值降低到7将会产生相对较小的树(或者正在使用的任何树),但是大的树仍然是可能的,但这种情况并不常见.

以前的方法(伪代码)

  1. Loop from min to max
  2. Closer to average numbers are added to the list more times
  3. A random element is selected from the list,更接近平均值的元素会增加更多,因此更有可能被选中.

好吧,这就像把一堆糖果放在一个袋子里然后随机挑一个.是的,慢.你对改进这个有什么看法?

插图:(不完全准确,但你看到了这个想法) 在此输入图像描述

注意:许多人建议使用钟形曲线,但问题是如何在这种意义上改变曲线的峰值以支持一侧.

Chu*_*ohr 9

我正在扩展生成n个随机数的想法,并取其平均值来获得钟形曲线效果."紧密度"参数控制曲线的陡峭程度.

编辑:中心限制定理支持对一组随机点求和以获得"正常"分布.使用偏置函数来影响特定方向是一种常见的技术,但我不是那里的专家.

为了解决问题末尾的注释,我通过操纵"内部"随机数来扭曲曲线.在这个例子中,我将它提升到你提供的指数.由于Random返回的值小于1,因此将其提升为任何幂仍将永远不会超过1.但平均偏向零,因为小于1的数字的正方形,立方体等甚至小于基数.exp = 1没有偏斜,而exp = 4具有非常显着的偏斜.

        private Random r = new Random();        

        public double RandomDist(double min, double max, int tightness, double exp)
        {
            double total = 0.0;
            for (int i = 1; i <= tightness; i++)
            {
                total += Math.Pow(r.NextDouble(), exp);
            }

            return ((total / tightness) * (max - min)) + min;
        }
Run Code Online (Sandbox Code Playgroud)

我针对exp运行了不同值的试验,生成了介于0到99之间的100,000个整数.以下是分布的结果.

倾斜的随机分布

我不确定峰值与exp值的关系,但exp越高,峰值出现在该范围内.

您还可以通过将循环内部的线更改为:来反转偏斜的方向:

 total += (1 - Math.Pow(r.NextDouble(), exp));
Run Code Online (Sandbox Code Playgroud)

......这会给曲线偏高的偏差.

编辑:那么,为了达到我们想要的峰值,我们怎么知道要制作什么"exp"?这是一个棘手的问题,可能是分析性的,但我是开发人员,而不是数学家.因此,应用我的交易,我进行了大量试验,收集了各种exp值的峰值数据,并通过Wolfram Alpha的立方拟合计算器运行数据,得到exp作为峰值函数的等式.

这是一组实现此逻辑的新函数.GetExp(...)函数实现了WolframAlpha找到的等式.

RandomBiasedPow(...)是感兴趣的函数.它返回指定范围内的随机数,但趋向于峰值.这种趋势的强度取决于紧密度参数.

    private Random r = new Random();

    public double RandomNormal(double min, double max, int tightness)
    {
        double total = 0.0;
        for (int i = 1; i <= tightness; i++)
        {
            total += r.NextDouble();
        }
        return ((total / tightness) * (max - min)) + min;
    }

    public double RandomNormalDist(double min, double max, int tightness, double exp)
    {
        double total = 0.0;
        for (int i = 1; i <= tightness; i++)
        {
            total += Math.Pow(r.NextDouble(), exp);
        }

        return ((total / tightness) * (max - min)) + min;
    }


    public double RandomBiasedPow(double min, double max, int tightness, double peak)
    {
        // Calculate skewed normal distribution, skewed by Math.Pow(...), specifiying where in the range the peak is
        // NOTE: This peak will yield unreliable results in the top 20% and bottom 20% of the range.
        //       To peak at extreme ends of the range, consider using a different bias function

        double total = 0.0;
        double scaledPeak = peak / (max - min) + min;

        if (scaledPeak < 0.2 || scaledPeak > 0.8)
        {
            throw new Exception("Peak cannot be in bottom 20% or top 20% of range.");
        }

        double exp = GetExp(scaledPeak);

        for (int i = 1; i <= tightness; i++)
        {
            // Bias the random number to one side or another, but keep in the range of 0 - 1
            // The exp parameter controls how far to bias the peak from normal distribution
            total += BiasPow(r.NextDouble(), exp);
        }

        return ((total / tightness) * (max - min)) + min;
    }

    public double GetExp(double peak)
    {
        // Get the exponent necessary for BiasPow(...) to result in the desired peak 
        // Based on empirical trials, and curve fit to a cubic equation, using WolframAlpha
        return -12.7588 * Math.Pow(peak, 3) + 27.3205 * Math.Pow(peak, 2) - 21.2365 * peak + 6.31735;
    }

    public double BiasPow(double input, double exp)
    {
        return Math.Pow(input, exp);
    }
Run Code Online (Sandbox Code Playgroud)

这是使用RandomBiasedPow(0,100,5,peak)的直方图,图例中显示了各种峰值.我向下舍入得到0到99之间的整数,将紧度设置为5,并尝试在20到80之间的峰值.(在极端峰值时事情变得很糟糕,所以我把它留下来,并在代码中发出警告.)你可以看到峰值应该在哪里.

各种峰值,紧密度= 5

接下来,我尝试将Tightness提升到10 ......

在此输入图像描述

分布更紧密,峰值仍然在它们应该的位置.它也很快!


pae*_*gun 5

既然您正在寻找一个值在一个点周围的正态分布,在边界内,为什么不使用 Random 来为您提供两个值,然后用它们从中间走一段距离?以下产生了我认为您需要的内容:

// NOTE: scoped outside of the function to be random
Random rnd = new Random();
int GetNormalizedRandomValue(int mid, int maxDistance)
{
    var distance = rnd.Next(0, maxDistance + 1);
    var isPositive = (rnd.Next() % 2) == 0;
    if (!isPositive)
    {
        distance = -distance;
    }

    return mid + distance;
}
Run Code Online (Sandbox Code Playgroud)

插入http://www.codeproject.com/Articles/25172/Simple-Random-Number-Generation使这更容易并正确规范化:

int GetNormalizedRandomValue(int mid, int maxDistance)
{
    int distance;
    do
    {
        distance = (int)((SimpleRNG.GetNormal() / 5) * maxDistance);
    } while (distance > maxDistance);
    return mid + distance;
}
Run Code Online (Sandbox Code Playgroud)

  • 第一种方法没有任何意义,它只是一种获得均匀分布的复杂方法。一个简单的 `rnd.(mid - maxDistance, mid + maxDistance)` 的工作原理是一样的。 (2认同)

Ben*_*son 5

这是实现这一目标的简单方法.既然您已经有详细说明如何生成正态分布的答案,并且有足够的资源,我将不再重复.相反,我将引用一个我将调用的方法GetNextNormal(),它应该从正态分布生成一个平均值为0且标准差为1的值.

public int Next(int min, int max, int center)
{
    int rand = GetNextNormal();
    if(rand >= 0)
        return center + rand*(max-center);
    return center + rand*(center-min);
}
Run Code Online (Sandbox Code Playgroud)

(这可以简化一点,为了清楚起见,我已经这样写了)

对于这是做什么的粗略图像,想象两个正态分布.它们都以你的为中心center,但是对于一个min是一个标准偏差,向左,而另一个,max是一个标准偏差,向右.现在想象一下将它们切成两半center.在左侧,保持标准差对应的标准差min,右侧标注对应的标准差max.

当然,正常分布不能保证在一个标准差内,因此您可能想要做两件事:

  • 添加一个额外的参数来控制分布的紧密程度
  • 如果您想要min并且max成为硬限制,则必须为这些边界之外的值添加拒绝.

一个完整的方法,有两个添加(再次保持一切为ints),可能看起来像;

public int Next(int min, int max, int center, int tightness)
{
    int rand = GetNextNormal();
    int candidate;

    do
    {
        if(rand >= 0)
            candidate = center + rand*(max-center)/tightness;
        else
            candidate = center + rand*(center-min)/tightness;
    } while(candidate < min || candidate > max);

    return candidate;
}
Run Code Online (Sandbox Code Playgroud)

如果您绘制此结果(特别是float/ double版本),它将不是最美丽的分布,但它应该足以满足您的需要.

编辑

我上面说的结果并不是特别漂亮.为了扩展,最明显的"丑陋"是中心点的不连续性,因为正态分布的峰值高度取决于其标准偏差.因此,您最终得到的分布将如下所示:

原始版本

(最小10,最大100和中心点70,使用3的"紧密度")

因此,虽然低于中心的值的概率等于上述概率,但结果将比一侧的平均值更紧密地"聚集"在一侧.如果这对你来说太难看了,或者你认为通过这样的分布生成特征的结果看起来太不自然了,我们可以添加一个额外的修改,称量中心的哪一边是按左边范围的比例选取的,或者中心权利.将其添加到代码中(假设您可以访问Random我刚刚调用的代码RandomGen),我们得到:

public int Next(int min, int max, int center, int tightness)
{
    int rand = Math.Abs(GetNextNormal());
    int candidate;

    do
    {
        if(ChooseSide())
            candidate = center + rand*(max-center)/tightness;
        else
            candidate = center - rand*(center-min)/tightness;
    } while(candidate < min || candidate > max);

    return candidate;
}

public bool ChooseSide(int min, int max, int center)
{
    return RandomGen.Next(min, max) >= center;
}
Run Code Online (Sandbox Code Playgroud)

为了比较,这将产生相同的最小值,最大值,中心和紧密度的分布是:

更顺畅的版本

正如您所看到的,现在这是频率连续的,以及一阶导数(给出平滑的峰值).这个版本相对于另一个版本的缺点是现在你更有可能在中心的一侧获得比另一侧更好的结果.中心现在是模态平均值,而不是平均值.因此,无论您更喜欢更顺畅的分销还是让中心成为分销的真正意义,都取决于您.