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

时间:2013-09-15 00:41:28

标签: c# .net algorithm random

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

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

以前的方法(伪代码ish)

  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,添加了更接近平均值的元素    更多,所以他们更有可能被选中。
  4. 好吧,这就像把一堆糖果放在一个袋子里然后随机挑一个。是的,慢。你对改进这个有什么看法?

    插图:(不完全准确,但你看到了这个想法) enter image description here

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

9 个答案:

答案 0 :(得分:9)

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

编辑:汇总一组随机点以获得"正常" Central Limit Theorem支持分发。使用偏置函数来影响特定方向是一种常见的技术,但我不是那里的专家。

为了解决问题末尾的注释,我通过操纵"内部"来扭曲曲线。随机数。在这个例子中,我将它提升到你提供的指数。由于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;
        }

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

Skewed Random Distribution

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

您还可以通过将循环内部的线更改为:

来反转偏斜的方向
 total += (1 - Math.Pow(r.NextDouble(), exp));

......这会在曲线的高端产生偏差。

编辑那么,我们怎么知道要做什么&#34; exp&#34;为了达到我们想要的高峰?这是一个棘手的问题,可能是分析性的,但我是开发人员,而不是数学家。因此,应用我的交易,我进行了大量试验,收集了各种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);
    }

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

Various Peak Values, Tightness=5

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

enter image description here

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

答案 1 :(得分: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);
}

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

对于这是做什么的粗略图像,想象两个正态分布。它们都以center为中心,但是对于minmax距离左侧是一个标准偏差,而对于另一个,center是一个标准偏差距离, 在右边。现在想象一下将它们切成两半min。在左侧,您保留的标准差与max相对应,右侧则与min对应。

当然,正常分布不能保持在一个标准差内,所以你可能想做两件事:

  • 添加一个额外的参数来控制分发的紧密程度
  • 如果您希望maxint成为硬限制,则必须为这些边界之外的值添加拒绝。

一个完整的方法,有两个添加(现在再次将所有内容保留为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; } ),可能看起来像;

float

如果您对此结果进行绘制(尤其是double / Random版本),则它不会是最美丽的发行版,但它应该足以满足您的目的。

修改

上面我说过这个结果并不是特别漂亮。为了扩展它,最明显的丑陋&#39;由于正态分布的峰值高度取决于其标准偏差,因此在中心点处是不连续的。因此,您最终得到的分布将如下所示:

original version

(最小10,最大100和中心点70,使用&#39;紧度&#39; 3)

因此,虽然低于中心的值的概率等于上述概率,但结果会更加紧密,并且会成为&#34;一边是平均而不是另一边。如果这对你来说太难看了,或者你认为通过像这样的分布生成特征的结果看起来太不自然了,我们可以添加一个额外的修改,称量中心的哪一侧被范围的比例选取在中心的左侧或右侧。将其添加到代码中(假设您可以访问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; } ),我们得到:

{{1}}

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

smoother version

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

答案 2 :(得分:4)

由于你正在寻找一个普通的分布,其值在一个点附近,在边界内,为什么不使用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;
}

插入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;
}

答案 3 :(得分:3)

我会做这样的事情:

  1. 计算均匀分布式双
  2. 使用它,使用公式进行正态分布(如果我记得你称之为“逆密度函数”?嗯,将[0,1]“后退”映射到累积概率的那个)或类似于计算所需的价值 - 例如您可以稍微调整正态分布,不仅取平均值和stddev /方差,而且取平均值和两个这样的值来处理min / max
  3. 围绕int,确保min,max等

答案 4 :(得分:3)

这是我的解决方案。 MyRandom类与Next()具有3个附加参数的等效函数。 中心 span 表示所需的范围,重试是重试次数,每次重试时,生成所需范围内的数字的概率应该是理论上增加50%。

static void Main()
{
    MyRandom myRnd = new MyRandom();
    List<int> results = new List<int>();

    Console.WriteLine("123456789012345\r\n");

    int bnd = 30;

    for (int ctr = 0; ctr < bnd; ctr++)
    {
        int nextAvg = myRnd.NextAvg(5, 16, 10, 2, 2);
        results.Add(nextAvg);

        Console.WriteLine(new string((char)9608, nextAvg));
    }


    Console.WriteLine("\r\n" + String.Format("Out of range: {0}%", results.Where(x => x < 8 || x > 12).Count() * 100 / bnd)); // calculate out-of-range percentage
    Console.ReadLine();
}

class MyRandom : Random
{
    public MyRandom() { }

    public int NextAvg(int min, int max, int center, int span, int retry)
    {
        int left = (center - span);
        int right = (center + span);

        if (left < 0 || right >= max)
        {
            throw new ArgumentException();
        }

        int next = this.Next(min, max);
        int ctr = 0;

        while (++ctr <= retry && (next < left || next > right))
        {
            next = this.Next(min, max);
        }

        return next;
    }
}

答案 5 :(得分:2)

你有两个选择:

  1. 总结来自N的{​​{1}}个随机数,这些随机数会收集(0,1/N)周围的结果以及0.5x_min的结果。数字x_max取决于结果的范围。计数越高,结果越窄。

    N
  2. 使用具有均值和标准差的实际正态分布。但这并不能保证最低或最高。

    Random rnd = new Random();
    int N=10;
    double r = 0;    
    for(int i=0; i<N; i++) { r+= rnd.NextDouble()/N; }
    double x = x_min+(x_max-x_min)*r;
    

答案 6 :(得分:1)

您可以使用MathNet.Numerics(mathdotnet.com)中的Normal分发类。

使用它的一个例子:

// Distribution with mean = 10, stddev = 1.25 (5 ~ 15 99.993%)
var dist = new MathNet.Numerics.Distributions.Normal(10, 1.25);
var samples = dist.Samples().Take(10000);
Assert.True(samples.Average().AlmostEqualInDecimalPlaces(10, 3));

您可以通过更改标准差(我使用的1.25)来调整点差。唯一的问题是,它偶尔会给你超出你想要的范围的值,所以你必须拥有它们。如果你想要某种更偏斜的东西,你也可以尝试从库中获得其他分布函数。

更新 - 示例类:

public class Random
{
    MathNet.Numerics.Distributions.Normal _dist;
    int _min, _max, _mean;

    public Random(int mean, int min, int max)
    {
        _mean = mean;
        _min = min;
        _max = max;
        var stddev = Math.Min(Math.Abs(mean - min), Math.Abs(max - mean)) / 3.0;
        _dist = new MathNet.Numerics.Distributions.Normal(mean, stddev);
    }

    public int Next()
    {
        int next;
        do
        {
            next = (int)_dist.Sample();
        } while (next < _min || next > _max);

        return next;
    }

    public static int Next(int mean, int min, int max)
    {
        return new Random(mean, min, max).Next();
    }
}

答案 7 :(得分:1)

是否有任何理由说分布必须实际上是钟形曲线?例如,使用:

public int RandomDist(int min, int max, int average)
{
  rnd = new Math.Random();
  n = rnd.NextDouble();
  if (n < 0.75)
  {
    return Math.Sqrt(n * 4 / 3) * (average - min) + min;
  } else {
    return Math.Sqrt(n * 4 - 3) * (max - average) + average;
  }
}

会在minmax之间给出一个数字,模式为average

答案 8 :(得分:0)

不确定这是你想要的,但是这里有一种方法可以绘制一个随机数,其分布从minavg以及从avg到{{1确保平均值等于max

假设从[平均平均]开始抽取概率p,从[平均最大值]开始概率1-p。预期值为avg。我们求解p:p.(min+avg)/2 + (1-p).(avg+max)/2 = p.min/2 + avg/2 + (1-p).max/2 = avg

生成器的工作方式如下:在p=(max-avg)/(max-min)中绘制一个随机数。如果小于[0 1],请从p中抽取一个随机数;否则,从[min avg]中抽取一个。

概率图是分段常数,[avg max]pminavg1-pavg。极端值不会受到惩罚。