22

に「平均」パラメータを追加できるようにしたいと思いますRandom.Next(Lower, Upper)。このメソッドにはminmaxおよびaverageパラメータがあります。しばらく前にテスト用にこのようなメソッドを作成しました (リストを使用し、ひどいものでした) ので、正しい実装を作成する方法についていくつかのアイデアが欲しいです。

この機能を持つ理由は、ゲーム内の多くの手続き型/ランダム イベントのためです。ほとんどの場合、樹木の高さを 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、平均に近い要素が多く追加されるため、選択される可能性が高くなります。

これは、袋にたくさんのキャンディーを放り込んでランダムに 1 つ選ぶようなものです。ええ、ゆっくり。これを改善するためのあなたの考えは何ですか?

イラスト: (正確ではありませんが、アイデアはわかります) ここに画像の説明を入力

注: 多くの人がベル カーブを提案していますが、問題は、この意味で一方の側に有利になるようにカーブのピークを変更する方法です。

4

9 に答える 9

12

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の 3 次近似計算機で実行して、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

次にタイトネスを10まで上げてみた…

ここに画像の説明を入力

分布はよりタイトで、ピークはまだあるべき場所にあります。それもかなり速いです!

于 2014-05-02T20:57:22.170 に答える
6

これを実現する簡単な方法を次に示します。正規分布を生成する方法を詳述した回答が既にあり、それに関するリソースがたくさんあるので、繰り返しません。代わりに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);
}

(これは少し単純化できます。わかりやすくするためにそのように書きました)

これが何をしているかの大まかなイメージとして、2 つの正規分布を想像してください。どちらも を中心にしていますcenterが、一方のminは 1 標準偏差離れた左側にあり、もうmax1 つは 1 標準偏差離れた右側にあります。で両方を半分に切ることを想像してみてくださいcenter。左側に に対応する標準偏差を保持minし、右側に に対応するものを保持しmaxます。

もちろん、正規分布が 1 つの標準偏差内に収まるとは限らないため、次の 2 つのことを行う必要があります。

  • 分布がどれほどタイトかを制御する追加のパラメーターを追加します
  • minハードリミットにしたい場合はmax、それらの境界外の値に対して拒否を追加する必要があります。

これら 2 つの追加 (ここでもすべてを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;
}

この結果 (特に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;
}

比較のために、これが同じ最小値、最大値、中心値、タイトネスで生成する分布は次のとおりです。

よりスムーズなバージョン

ご覧のとおり、これは周波数と一次導関数で連続しています (滑らかなピークが得られます)。このバージョンが他のバージョンよりも不利な点は、中心の一方の側で結果が得られる可能性が高くなることです。中心は、平均ではなくモーダル平均になりました。したがって、より滑らかな分布を好むか、中心を分布の真の平均にするかは、あなた次第です。

于 2014-05-02T03:09:01.327 に答える
5

範囲内の点の周りの値を持つ正規分布を探しているので、代わりに Random を使用して 2 つの値を取得し、それを使用して中央から距離を移動してみませんか? 以下は、あなたが必要だと私が信じているものをもたらします:

// 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;
}
于 2013-09-15T00:56:20.687 に答える
3

これが私の解決策です。MyRandom クラスは、3 つの追加パラメーターを持つ Next() と同等の機能を備えています。centerspanは望ましい範囲を示し、retryは再試行回数です。再試行のたびに、望ましい範囲内の数値が生成される確率は、理論的には正確に 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;
    }
}
于 2014-04-28T09:10:28.983 に答える
3

ここには 2 つの選択肢があります。

  1. が周囲の結果を収集するN乱数を合計し、結果をおよび 内でスケールします。この数は、結果がどれだけ狭いかによって異なります。カウントが高いほど、結果が絞り込まれます。(0,1/N)0.5x_minx_maxN

    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;
    
  2. 平均と標準偏差を含む実際の正規分布を使用します。ただし、これは最小または最大を保証するものではありません。

    public double RandomNormal(double mu, double sigma)
    {
        return NormalDistribution(rnd.NextDouble(), mu, sigma);
    }
    public double RandomNormal()
    {
        return RandomNormal(0d, 1d);
    }
    /// <summary>
    /// Normal distribution
    /// </summary>
    /// <arg name="probability">probability value 0..1</arg>
    /// <arg name="mean">mean value</arg>
    /// <arg name="sigma">std. deviation</arg>
    /// <returns>A normal distribution</returns>
    public double NormalDistribution(double probability, double mean, double sigma)
    {
        return mean+sigma*NormalDistribution(probability);
    }
    /// <summary>
    /// Normal distribution
    /// </summary>
    /// <arg name="probability">probability value 0.0 to 1.0</arg>
    /// <see cref="NormalDistribution(double,double,double)"/>
    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;
    }
    
于 2013-10-30T20:19:40.060 に答える
2

分布が実際にベル カーブでなければならない理由はありますか? たとえば、次を使用します。

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です。

于 2014-05-02T21:00:37.963 に答える
1

MathNet.Numerics ( mathdotnet.com )の正規分布クラスを使用できます。

使用例:

// 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();
    }
}
于 2014-05-02T01:47:34.517 に答える
0

これがあなたが望むものかどうかはわかりませんが、平均が に等しいことを保証しながら、mintoavgと from から一様な分布を持つ乱数を描画する方法を次に示します。avgmaxavg

[min avg] からの引き分けの確率 p と [avg max] からの確率 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]ます。それ以外の場合は、 から 1 つを描画し[avg max]ます。

確率のプロットは、pfrom mintoavgおよび1-pfrom avgtoの区分定数maxです。極端な値はペナルティを受けません。

于 2014-05-02T22:39:22.247 に答える