我希望能够向Random.Next(Lower, Upper)
添加“平均值”参数。此方法包含min
,max
和average
参数。我创建了一个像这样的方法一段时间用于测试(它使用了列表并且非常糟糕),所以我想了解如何编写正确的实现。
拥有此功能的原因是我游戏中的许多程序/随机事件。假设你希望大多数时候树木高10单位,但仍然可以低至5或15.正常Random.Next(5,15)
会返回结果,但这种方法会有更多的钟形曲线朝向它结果。意义10将是最常见的,并且在每个方向上出去都不太常见。例如,将平均值降低到7会产生相对较小的树木(或者正在使用的任何树木),但是大的树木仍然是可能的,但不常见。
以前的方法(伪代码ish)
答案 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个整数。以下是分布的结果。
我不确定峰值与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之间的峰值。(在极端峰值时事情变得很糟糕,所以我把它留下来,并在代码中发出警告。)你可以看到峰值应该在哪里。
接下来,我尝试将Tightness提升到10 ......
分布更紧密,峰值仍然在应有的位置。它也很快!
答案 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
为中心,但是对于min
,max
距离左侧是一个标准偏差,而对于另一个,center
是一个标准偏差距离, 在右边。现在想象一下将它们切成两半min
。在左侧,您保留的标准差与max
相对应,右侧则与min
对应。
当然,正常分布不能保持在一个标准差内,所以你可能想做两件事:
max
和int
成为硬限制,则必须为这些边界之外的值添加拒绝。一个完整的方法,有两个添加(现在再次将所有内容保留为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;由于正态分布的峰值高度取决于其标准偏差,因此在中心点处是不连续的。因此,您最终得到的分布将如下所示:
(最小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}}
为了进行比较,这将产生具有相同的最小值,最大值,中心和紧密度的分布:
正如您所看到的,现在这个频率是连续的,也是一阶导数(给出平滑的峰值)。这个版本相对于另一个版本的缺点是,现在您更有可能在中心的一侧获得比另一侧更好的结果。中心现在是模态平均值,而不是平均值。因此,无论您更喜欢更顺畅的分销还是让中心成为分销的真正意义,都取决于您。
答案 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)
我会做这样的事情:
答案 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)
你有两个选择:
总结来自N
的{{1}}个随机数,这些随机数会收集(0,1/N)
周围的结果以及0.5
和x_min
的结果。数字x_max
取决于结果的范围。计数越高,结果越窄。
N
使用具有均值和标准差的实际正态分布。但这并不能保证最低或最高。
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;
}
}
会在min
和max
之间给出一个数字,模式为average
。
答案 8 :(得分:0)
不确定这是你想要的,但是这里有一种方法可以绘制一个随机数,其分布从min
到avg
以及从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]
从p
到min
,avg
从1-p
到avg
。极端值不会受到惩罚。