私は、範囲内の各数字に重みが与えられる可能性のある数字の範囲から乱数を選択する(良い)方法を考案しようとしています。簡単に言うと、数字の範囲(0、1、2)で、0が選択される確率が80%、1が確率10%、2が確率10%の数字を選択します。
私の大学の統計クラスから約8年が経過しているので、現時点では、このための適切な式が私を免れていると想像できます。
ここに私が思いついた「安くて汚い」方法があります。このソリューションではColdFusionを使用します。あなたの好きな言語を使用できます。私はプログラマーです。移植を処理できると思います。最終的に私のソリューションはGroovyである必要があります-CFで簡単に記述/テストするのは簡単だから、これをColdFusionで書きました。
public function weightedRandom( Struct options ) {
var tempArr = [];
for( var o in arguments.options )
{
var weight = arguments.options[ o ] * 10;
for ( var i = 1; i<= weight; i++ )
{
arrayAppend( tempArr, o );
}
}
return tempArr[ randRange( 1, arrayLen( tempArr ) ) ];
}
// test it
opts = { 0=.8, 1=.1, 2=.1 };
for( x = 1; x<=10; x++ )
{
writeDump( weightedRandom( opts ) );
}
より良い解決策を探しています。改善や代替案を提案してください。
リジェクションサンプリング (ソリューションなど)が最初に思い浮かぶことです。これにより、重み分布によって設定された要素を含むルックアップテーブルを構築し、テーブル内のランダムな場所を選択してそれを返します。 。実装の選択として、仕様を取り、仕様の分布に基づいて値を返す関数を返す高次関数を作成します。これにより、呼び出しごとにテーブルを作成する必要がなくなります。欠点は、テーブルを構築するアルゴリズムのパフォーマンスがアイテムの数に対して線形であり、大きな仕様(または{0:0.99999、1 :0.00001})。利点は、値の選択に一定の時間がかかることです。これは、パフォーマンスが重要な場合に望ましい場合があります。 JavaScriptの場合:
function weightedRand(spec) {
var i, j, table=[];
for (i in spec) {
// The constant 10 below should be computed based on the
// weights in the spec for a correct and optimal table size.
// E.g. the spec {0:0.999, 1:0.001} will break this impl.
for (j=0; j<spec[i]*10; j++) {
table.Push(i);
}
}
return function() {
return table[Math.floor(Math.random() * table.length)];
}
}
var Rand012 = weightedRand({0:0.8, 1:0.1, 2:0.1});
Rand012(); // random in distribution...
別の戦略は、[0,1)
の乱数を選択し、重みを合計する重み仕様を反復処理することです。乱数が合計より小さい場合、関連する値を返します。もちろん、これは重みの合計が1であることを前提としています。このソリューションには初期費用はありませんが、仕様のエントリ数に比例する平均的なアルゴリズムのパフォーマンスがあります。たとえば、JavaScriptの場合:
function weightedRand2(spec) {
var i, sum=0, r=Math.random();
for (i in spec) {
sum += spec[i];
if (r <= sum) return i;
}
}
weightedRand2({0:0.8, 1:0.1, 2:0.1}); // random in distribution...
0〜1の乱数Rを生成します。
Rが[0、0.1)の場合-> 1
Rが[0.1、0.2)の場合-> 2
Rが[0.2、1]の場合-> 3
0から1の間の数値を直接取得できない場合は、必要な精度を生成できる範囲の数値を生成します。たとえば、次の重みがある場合
(1、83.7%)および(2、16.3%)、1から1000までの数値をロールします。1-837は1です。838-1000は2です。
これは、Javaで@trinithisが書いたものの多かれ少なかれ汎用化されたバージョンです。乱雑な丸めエラーを避けるために、floatではなくintを使用しました。
static class Weighting {
int value;
int weighting;
public Weighting(int v, int w) {
this.value = v;
this.weighting = w;
}
}
public static int weightedRandom(List<Weighting> weightingOptions) {
//determine sum of all weightings
int total = 0;
for (Weighting w : weightingOptions) {
total += w.weighting;
}
//select a random value between 0 and our total
int random = new Random().nextInt(total);
//loop thru our weightings until we arrive at the correct one
int current = 0;
for (Weighting w : weightingOptions) {
current += w.weighting;
if (random < current)
return w.value;
}
//shouldn't happen.
return -1;
}
public static void main(String[] args) {
List<Weighting> weightings = new ArrayList<Weighting>();
weightings.add(new Weighting(0, 8));
weightings.add(new Weighting(1, 1));
weightings.add(new Weighting(2, 1));
for (int i = 0; i < 100; i++) {
System.out.println(weightedRandom(weightings));
}
}
どの言語を使用するかわからないので、javascriptの3つのソリューションを示します。必要に応じて、最初の2つのうちの1つが機能する可能性がありますが、3つ目はおそらく大きな数字のセットで実装するのが最も簡単です。
function randomSimple(){
return [0,0,0,0,0,0,0,0,1,2][Math.floor(Math.random()*10)];
}
function randomCase(){
var n=Math.floor(Math.random()*100)
switch(n){
case n<80:
return 0;
case n<90:
return 1;
case n<100:
return 2;
}
}
function randomLoop(weight,num){
var n=Math.floor(Math.random()*100),amt=0;
for(var i=0;i<weight.length;i++){
//amt+=weight[i]; *alternative method
//if(n<amt){
if(n<weight[i]){
return num[i];
}
}
}
weight=[80,90,100];
//weight=[80,10,10]; *alternative method
num=[0,1,2]
どう?
int [] numbers = {0,0,0,0,0,0,0,0,1,2};
次に、数字からランダムに選択できます。0の場合は80%、1 10%、2 10%です。
私は次を使用します
_function weightedRandom(min, max) {
return Math.round(max / (Math.random() * max + min));
}
_
これは、「x」の逆関数(xは最小値と最大値の間のランダム)を使用して重み付き結果を生成する「重み付き」ランダムです。最小値は最も重い要素で、最大値は最も軽い(結果が得られる可能性が最も低い)
したがって、基本的にweightedRandom(1, 5)
を使用すると、1を取得する可能性が2を超える可能性があり、3を超える可能性があり、4を超える可能性があり、5を超える可能性があります。
あなたのユースケースには役に立たないかもしれませんが、おそらくこの同じ質問をグーグルで探している人々には役にたつでしょう。
100回繰り返した後、次のことがわかりました。
_==================
| Result | Times |
==================
| 1 | 55 |
| 2 | 28 |
| 3 | 8 |
| 4 | 7 |
| 5 | 2 |
==================
_
入力と比率は次のとおりです:0(80%)、1(10%)、2(10%)
簡単に視覚化できるようにそれらを引き出します。
0 1 2
-------------------------------------________+++++++++
合計重量を合計し、合計比率をTRと呼びます。したがって、この場合は100です。ランダムに(0-TR)または(この場合は0〜100)から番号を取得できます。合計が100である。乱数の場合はRNと呼びます。
したがって、合計重量としてTRがあり、0からTRの間の乱数としてRNがあります。
0から100までのランダムな#を選んだとしましょう。21と言います。実際は21%です。
これを入力番号に変換/一致させる必要がありますが、どうすればよいですか?
各ウェイト(80、10、10)をループし、既にアクセスしたウェイトの合計を保持します。ループしている重みの合計が乱数RN(この場合は21)よりも大きい場合、ループを停止してその要素の位置を返します。
double sum = 0;
int position = -1;
for(double weight : weight){
position ++;
sum = sum + weight;
if(sum > 21) //(80 > 21) so break on first pass
break;
}
//position will be 0 so we return array[0]--> 0
乱数(0から100まで)が83だとしましょう。もう一度やってみましょう:
double sum = 0;
int position = -1;
for(double weight : weight){
position ++;
sum = sum + weight;
if(sum > 83) //(90 > 83) so break
break;
}
//we did two passes in the loop so position is 1 so we return array[1]---> 1
これはMathematicaにありますが、別の言語にコピーするのは簡単です。ゲームで使用し、小数の重みを処理できます。
weights = {0.5,1,2}; // The weights
weights = N@weights/Total@weights // Normalize weights so that the list's sum is always 1.
min = 0; // First min value should be 0
max = weights[[1]]; // First max value should be the first element of the newly created weights list. Note that in Mathematica the first element has index of 1, not 0.
random = RandomReal[]; // Generate a random float from 0 to 1;
For[i = 1, i <= Length@weights, i++,
If[random >= min && random < max,
Print["Chosen index number: " <> ToString@i]
];
min += weights[[i]];
If[i == Length@weights,
max = 1,
max += weights[[i + 1]]
]
]
(リストの最初の要素のインデックスは0に等しい)と言っていますこの背後にある考え方は、正規化されたリストを持つことですweightsの可能性があるweights [n]インデックスを返すn。したがって、ステップnでの最小値と最大値の間の距離はweights [n] 。最小値からの合計距離(これを0にする)と最大値はリストの合計weightsです。
この背後にある良いことは、ループに配列やネストを追加しないことです。これにより、実行時間が大幅に増加します。
weightsリストを正規化し、いくつかのコードを削除する必要のないC#のコードを次に示します。
int WeightedRandom(List<float> weights) {
float total = 0f;
foreach (float weight in weights) {
total += weight;
}
float max = weights [0],
random = Random.Range(0f, total);
for (int index = 0; index < weights.Count; index++) {
if (random < max) {
return index;
} else if (index == weights.Count - 1) {
return weights.Count-1;
}
max += weights[index+1];
}
return -1;
}
8年遅れですが、ここに3行のソリューションがあります。
1)確率質量関数の配列を準備する
pmf [array_index] = P(X = array_index):
var pmf = [0.8, 0.1, 0.1]
2)対応する累積分布関数の配列を準備します
cdf [array_index] = F(X = array_index):
var cdf = pmf.map((sum => value => sum += value)(0))
// [0.8, 0.9, 1]
3a)乱数を生成します。
3b)この数以上の要素の配列を取得します。
3c)長さを返します。
cdf.filter(el => Math.random() >= el).length
私はスロットマシンを所有しており、以下のコードを使用して乱数を生成しました。 probabilitiesSlotMachineでは、キーはスロットマシンの出力であり、値は重量を表します。
const probabilitiesSlotMachine = [{0 : 1000}, {1 : 100}, {2 : 50}, {3 : 30}, {4 : 20}, {5 : 10}, {6 : 5}, {7 : 4}, {8 : 2}, {9 : 1}]
var allSlotMachineResults = []
probabilitiesSlotMachine.forEach(function(obj, index){
for (var key in obj){
for (var loop = 0; loop < obj[key]; loop ++){
allSlotMachineResults.Push(key)
}
}
});
ランダム出力を生成するには、次のコードを使用します。
const random = allSlotMachineResults[Math.floor(Math.random() * allSlotMachineResults.length)]
確率と残りの乱数の連続チェックを使用することをお勧めします。
この関数は、最初に戻り値を最後の可能なインデックスに設定し、残りのランダム値が実際の確率より小さくなるまで繰り返します。
確率は合計する必要があります。
function getRandomIndexByProbability(probabilities) {
var r = Math.random(),
index = probabilities.length - 1;
probabilities.some(function (probability, i) {
if (r < probability) {
index = i;
return true;
}
r -= probability;
});
return index;
}
var i,
probabilities = [0.8, 0.1, 0.1],
count = probabilities.map(function () { return 0; });
for (i = 0; i < 1e6; i++) {
count[getRandomIndexByProbability(probabilities)]++;
}
console.log(count);
.as-console-wrapper { max-height: 100% !important; top: 0; }