上記のコメントで述べたように、コードを複雑にする前にこれをプロファイルすることをお勧めします。簡単なfor
ループサミングサイコロは、複雑な数式やテーブル作成/検索よりも理解と修正がはるかに簡単です。重要な問題を解決していることを確認するために、常に最初にプロファイルを作成してください。;)
とはいえ、洗練された確率分布を一気にサンプリングするには、主に2つの方法があります。
1.累積確率分布
単一の一様ランダム入力のみを使用して、連続確率分布からサンプリングする巧妙なトリックがあります。それは関係していた累積分布関数をその「値になっていない確率は何の答えも大きく xよりは?」
この関数は減少せず、0から始まり、そのドメインで1に上昇します。2つの6面ダイスの合計の例を以下に示します。
累積分布関数に計算に便利な逆関数がある場合(またはベジエ曲線のような区分的関数で近似できる場合)、これを使用して元の確率関数からサンプリングできます。
逆関数は、0から1までのドメインを元のランダムプロセスの各出力にマッピングされた間隔に分割し、それぞれの流域面積が元の確率に一致するように処理します。(これは、連続分布では無限に真です。サイコロのような離散分布では、慎重な丸めを適用する必要があります)
これを使用して2d6をエミュレートする例を次に示します。
int SimRoll2d6()
{
// Get a random input in the half-open interval [0, 1).
float t = Random.Range(0f, 1f);
float v;
// Piecewise inverse calculated by hand. ;)
if(t <= 0.5f)
{
v = (1f + sqrt(1f + 288f * t)) * 0.5f;
}
else
{
v = (25f - sqrt(289f - 288f * t)) * 0.5f;
}
return floor(v + 1);
}
これと比較してください:
int NaiveRollNd6(int n)
{
int sum = 0;
for(int i = 0; i < n; i++)
sum += Random.Range(1, 7); // I'm used to Range never returning its max
return sum;
}
コードの明快さと柔軟性の違いについて私が言っていることを見てください。素朴な方法はループを使用した素朴な方法かもしれませんが、短くてシンプルで、その動作がすぐにわかり、さまざまなダイサイズと数に簡単にスケーリングできます。累積分布コードに変更を加えるには、自明ではない数学が必要であり、明らかな間違いを犯さずに簡単に破って予期しない結果を引き起こす可能性があります。(私は上に行っていないことを望みます)
したがって、明確なループをなくす前に、それがこの種の犠牲に値するパフォーマンスの問題であることを絶対に確認してください。
2.エイリアスメソッド
累積分布法は、累積分布関数の逆関数を単純な数式で表現できる場合にうまく機能しますが、それは必ずしも簡単ではなく、可能性さえありません。離散分布の信頼できる代替手段は、エイリアスメソッドと呼ばれるものです。
これにより、2つの独立した均一に分布したランダム入力を使用して、任意の離散確率分布からサンプリングできます。
これは、左下のような分布を取ります(エイリアスの方法では相対的な重みを考慮しているため、面積/重みが1にならないことを心配しないでください)。正しい場所:
- 結果ごとに1つの列があります。
- 各列は最大2つの部分に分割され、各部分は元の結果の1つに関連付けられます。
- 各結果の相対的な面積/重量は保持されます。
(サンプリング方法に関するこの優れた記事の画像に基づく図)
コードでは、各列から代替結果を選択する確率と、その代替結果のアイデンティティ(または「エイリアス」)を表す2つのテーブル(または2つのプロパティを持つオブジェクトのテーブル)でこれを表します。その後、次のように分布からサンプリングできます。
int SampleFromTables(float[] probabiltyTable, int[] aliasTable)
{
int column = Random.Range(0, probabilityTable.Length);
float p = Random.Range(0f, 1f);
if(p < probabilityTable[column])
{
return column;
}
else
{
return aliasTable[column];
}
}
これには少しセットアップが必要です。
考えられるすべての結果の相対確率を計算します(したがって、1000d6をローリングしている場合、1000から6000までのすべての合計を取得する方法の数を計算する必要があります)
各結果のエントリを持つテーブルのペアを作成します。完全な方法はこの回答の範囲を超えているため、エイリアスメソッドアルゴリズムのこの説明を参照することを強くお勧めします。
これらのテーブルを保存し、このディストリビューションから新しいランダムダイスロールが必要になるたびにそれらを参照し直してください。
これは時空のトレードオフです。事前計算のステップはやや網羅的であり、結果の数に比例してメモリを確保する必要があります(1000d6の場合でも、1桁のキロバイトを話しているので、睡眠を失うことはありません)分布がどれほど複雑であっても一定時間です。
これらのメソッドのいずれかが何らかの用途に役立つことを願っています(または、単純なメソッドの単純さはループに要する時間の価値があると確信していること);)