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

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

拥有此function的原因是我游戏中的许多程序/随机事件。 假设你希望大多数时候树木高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 ,更接近平均值的元素被添加更多,因此更有可能选择它们。

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

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

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

我正在扩大生成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; } 

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

倾斜的随机分布

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

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

  total += (1 - Math.Pow(r.NextDouble(), exp)); 

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

编辑:那么,为了达到我们想要的峰值,我们怎么知道要制作什么“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); } 

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

各种峰值,紧密度= 5

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

在此处输入图像描述

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

这是实现这一目标的简单方法。 既然你已经有详细说明如何生成正态分布的答案,并且有足够的资源,我将不再重复。 相反,我将引用一个我称之为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); } 

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

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

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

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

一个完整的方法,有两个添加(再次将所有内容保存为int s),可能看起来像;

 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版本),它将不是最美丽的分布,但它应该足以满足您的需要。

编辑

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

原始版本

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

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

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

更顺畅的版本

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

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

我会做这样的事情:

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

这是我的解决方案。 MyRandom类具有与Next()相同的函数,带有3个附加参数。 中心跨度表示理想的范围, 重试是重试计数,每次重试时,在理论上产生数量在期望范围内的概率应该增加到恰好50%。

 static void Main() { MyRandom myRnd = new MyRandom(); List results = new List(); 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; } } 

你有两个选择:

  1. (0,1/N) N随机数求和,得到0.5左右的结果,得到x_minx_max的结果。 数字N取决于结果的狭窄程度。 计数越高,结果越窄。

     Random rnd = new Random(); int N=10; double r = 0; for(int i=0; i 
  2. 使用具有均值和标准差的实际正态分布。 但这并不能保证最低或最高。

     public double RandomNormal(double mu, double sigma) { return NormalDistribution(rnd.NextDouble(), mu, sigma); } public double RandomNormal() { return RandomNormal(0d, 1d); } ///  /// Normal distribution ///  /// probability value 0..1 /// mean value /// std. deviation /// A normal distribution public double NormalDistribution(double probability, double mean, double sigma) { return mean+sigma*NormalDistribution(probability); } ///  /// Normal distribution ///  /// probability value 0.0 to 1.0 ///  public double NormalDistribution(double probability) { return Math.Sqrt(2)*InverseErrorFunction(2*probability-1); } public double InverseErrorFunction(double P) { double Y, A, B, X, Z, W, WI, SN, SD, F, Z2, SIGMA; const double A1=-.5751703, A2=-1.896513, A3=-.5496261E-1; const double B0=-.1137730, B1=-3.293474, B2=-2.374996, B3=-1.187515; const double C0=-.1146666, C1=-.1314774, C2=-.2368201, C3=.5073975e-1; const double D0=-44.27977, D1=21.98546, D2=-7.586103; const double E0=-.5668422E-1, E1=.3937021, E2=-.3166501, E3=.6208963E-1; const double F0=-6.266786, F1=4.666263, F2=-2.962883; const double G0=.1851159E-3, G1=-.2028152E-2, G2=-.1498384, G3=.1078639E-1; const double H0=.9952975E-1, H1=.5211733, H2=-.6888301E-1; X=P; SIGMA=Math.Sign(X); if(P<-1d||P>1d) throw new System.ArgumentException(); Z=Math.Abs(X); if(Z>.85) { A=1-Z; B=Z; W=Math.Sqrt(-Math.Log(A+A*B)); if(W>=2.5) { if(W>=4.0) { WI=1.0/W; SN=((G3*WI+G2)*WI+G1)*WI; SD=((WI+H2)*WI+H1)*WI+H0; F=W+W*(G0+SN/SD); } else { SN=((E3*W+E2)*W+E1)*W; SD=((W+F2)*W+F1)*W+F0; F=W+W*(E0+SN/SD); } } else { SN=((C3*W+C2)*W+C1)*W; SD=((W+D2)*W+D1)*W+D0; F=W+W*(C0+SN/SD); } } else { Z2=Z*Z; F=Z+Z*(B0+A1*Z2/(B1+Z2+A2/(B2+Z2+A3/(B3+Z2)))); } Y=SIGMA*F; return Y; } 

您可以使用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(); } } 

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

 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

不确定这是你想要的,但是这里有一种方法来绘制一个随机数,其分布从minavg和从avgmax均匀,同时确保平均值等于avg

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

生成器的工作原理如下:在[0 1]绘制一个随机数。 如果小于p ,则从[min avg]抽取一个随机数; 否则,从[avg max]抽取一个。

概率图是分段常数, pminavg1-pavgmax 。 极端值不会受到惩罚。