web-dev-qa-db-ja.com

確率付き乱数

私は、各番号に特定の確率が発生するかどうかにかかわらず、特定の範囲内で乱数を生成する最良の方法(Javaなど)がどうなるのだろうかと考えています。

例えば.

[1; 3]内から次の確率でランダムな整数を生成します。

P(1)= 0.2
P(2)= 0.3
P(3)= 0.5


現在、[0; 100]内でランダムな整数を生成するアプローチを検討しており、以下を実行しています。

[0; 20]以内の場合->乱数1を取得しました。
[21; 50]以内の場合->乱数2を取得しました。
[51; 100]以内の場合->乱数3を取得しました。

あなたは何と言うでしょう?

40
marc wellman

あなたのものはすでにかなり良い方法であり、あらゆる範囲でうまく機能します。

考えてみてください:別の可能性は、定数乗数で乗算することで分数を取り除き、この乗数のサイズで配列を構築することです。あなたが得る10で乗算

P(1) = 2
P(2) = 3
P(3) = 5

次に、逆の値を使用して配列を作成します。「1」は要素1と2に、「2」は3〜6などになります。

P =(1,1、2,2,2、2、3,3,3,3,3);

代わりに、この配列からランダムな要素を選択できます。


(追加。)kiruwkaのコメントの例の確率を使用します。

int[] numsToGenerate           = new int[]    { 1,   2,    3,   4,    5   };
double[] discreteProbabilities = new double[] { 0.1, 0.25, 0.3, 0.25, 0.1 };

すべての整数につながる最小の乗数は20です。

2, 5, 6, 5, 2

したがって、numsToGenerateの長さは20になり、次の値になります。

1 1
2 2 2 2 2
3 3 3 3 3 3
4 4 4 4 4
5 5

分布は正確に同じです。たとえば、「1」の可能性は20のうち2で、まだ0.1です。

これは、元の確率の合計がすべて1になることに基づいています。そうでない場合は、合計にこの同じ係数を掛けます(これが配列の長さにもなります)。

33
usr2564301

少し前に、この問題を解決するためのヘルパークラスを作成しました。ソースコードは、概念を十分に明確に示す必要があります。

public class DistributedRandomNumberGenerator {

    private Map<Integer, Double> distribution;
    private double distSum;

    public DistributedRandomNumberGenerator() {
        distribution = new HashMap<>();
    }

    public void addNumber(int value, double distribution) {
        if (this.distribution.get(value) != null) {
            distSum -= this.distribution.get(value);
        }
        this.distribution.put(value, distribution);
        distSum += distribution;
    }

    public int getDistributedRandomNumber() {
        double Rand = Math.random();
        double ratio = 1.0f / distSum;
        double tempDist = 0;
        for (Integer i : distribution.keySet()) {
            tempDist += distribution.get(i);
            if (Rand / ratio <= tempDist) {
                return i;
            }
        }
        return 0;
    }

}

クラスの使用法は次のとおりです。

DistributedRandomNumberGenerator drng = new DistributedRandomNumberGenerator();
drng.addNumber(1, 0.3d); // Adds the numerical value 1 with a probability of 0.3 (30%)
// [...] Add more values

int random = drng.getDistributedRandomNumber(); // Generate a random number

機能を検証するためのドライバーのテスト:

    public static void main(String[] args) {
        DistributedRandomNumberGenerator drng = new DistributedRandomNumberGenerator();
        drng.addNumber(1, 0.2d);
        drng.addNumber(2, 0.3d);
        drng.addNumber(3, 0.5d);

        int testCount = 1000000;

        HashMap<Integer, Double> test = new HashMap<>();

        for (int i = 0; i < testCount; i++) {
            int random = drng.getDistributedRandomNumber();
            test.put(random, (test.get(random) == null) ? (1d / testCount) : test.get(random) + 1d / testCount);
        }

        System.out.println(test.toString());
    }

このテストドライバーのサンプル出力:

{1=0.20019100000017953, 2=0.2999349999988933, 3=0.4998739999935438}
28
trylimits

あなたはすでにあなたの質問で実装を書きました。 ;)

final int ran = myRandom.nextInt(100);
if (ran > 50) { return 3; }
else if (ran > 20) { return 2; } 
else { return 1; }

次のようなスイッチテーブルで結果を計算することにより、より複雑な実装でこれを高速化できます。

t[0] = 1; t[1] = 1; // ... one for each possible result
return t[ran];

ただし、これがパフォーマンスのボトルネックであり、1秒間に数百回呼び出される場合にのみ使用してください。

8
TwoThe

すべてのn値O(n)を検索する代わりにパフォーマンスの問題がある場合

o(log n)のコストがかかるバイナリ検索を実行できます

Random r=new Random();      
double[] weights=new double[]{0.1,0.1+0.2,0.1+0.2+0.5};
// end of init
double random=r.nextDouble();
// next perform the binary search in weights array

多くのweights要素がある場合、平均してlog2(weights.length)にアクセスする必要があります。

5
chro

100の配列の代わりに10の配列を使用することでストレージを削減できますが、選択した特定の数値に対してアプローチは適切です。ただし、このアプローチは、多数の結果または1/eなどの確率を持つ結果にうまく一般化できませんまたは1/PI

潜在的に優れたソリューションは、 エイリアステーブル を使用することです。エイリアスメソッドは、nの結果のテーブルを設定するためにO(n)の作業を取りますが、結果の数に関係なく生成するのに一定の時間です。

4
pjs

これを試してください:この例では、文字の配列を使用しますが、整数配列で置き換えることができます。

重みリストには、各文字に関連する確率が含まれています。それは私の文字セットの確率分布を表します。

各charのweightsumリストには、実際の確率と任意の先行確率の合計を格納しました。

たとえば、weightsumでは、「C」に対応する3番目の要素は65です。
P( 'A')+ P( 'B)+ P(' C ')= P(X => c)
10 + 20 + 25 = 65

したがって、weightsumは私の文字セットの累積分布を表します。 weightsumには次の値が含まれます。

Hに対応する8番目の要素がより大きなギャップ(もちろん彼の確率と同様に80)を持っていることは簡単にわかります。

        List<Character> charset =   Arrays.asList('A','B','C','D','E','F','G','H','I','J');
        List<Integer> weight = Arrays.asList(10,30,25,60,20,70,10,80,20,30);
        List<Integer>  weightsum = new ArrayList<>();

        int i=0,j=0,k=0;
        Random Rnd = new Random();

        weightsum.add(weight.get(0));

        for (i = 1; i < 10; i++)
            weightsum.add(weightsum.get(i-1) + weight.get(i));

次に、サイクルを使用して、charsetから30個のランダムなchar抽出を取得します。それぞれが累積確率に応じて描画されます。

K iには、0からweightsumで割り当てられた最大値までの乱数が格納されていました。次に、kよりも大きい数値のweightsumを検索します。weightsumの数値の位置は、charsetのcharの同じ位置に対応します。

   for (j = 0; j < 30; j++)
   {
   Random r = new Random();
   k =   r.nextInt(weightsum.get(weightsum.size()-1));

   for (i = 0; k > weightsum.get(i); i++) ;
   System.out.print(charset.get(i));
   }

コードはcharのそのシーケンスを与えます:

HHFAIIDFBDDDHFICJHACCDFJBGBHHB

数学をしましょう!

A = 2
B = 4
C = 3
D = 5
E = 0
F = 4
G = 1
H = 6
I = 3
J = 2

合計:30
DとHの出現回数を増やしたい(70%と80%の確率)
Otherwinse Eはまったく登場しませんでした。 (10%の確率)

1
Giulio Pilotto

Javaを要求したとしても、ここにpythonコードがありますが、非常によく似ています。

# weighted probability

theta = np.array([0.1,0.25,0.6,0.05])
print(theta)

sample_axis = np.hstack((np.zeros(1), np.cumsum(theta))) 
print(sample_axis)

[0。 0.1 0.35 0.95 1.]。これは累積分布を表します。

均一な分布を使用して、この単位範囲でインデックスを描画できます。

def binary_search(axis, q, s, e):
    if e-s <= 1:
        print(s)
        return s
    else: 
        m = int( np.around( (s+e)/2 ) )
        if q < axis[m]:
            binary_search(axis, q, s, m)
        else:
            binary_search(axis, q, m, e)



range_index = np.random.Rand(1)
print(range_index)
q = range_index
s = 0
e = sample_axis.shape[0]-1
binary_search(sample_axis, q, 0, e)
0
Albert Chen

別の post でpjsが指している論文を参照した後、インタビューのためにこのクラスを作成し、base64テーブルの人口をさらに最適化することができます。結果は驚くほど高速で、初期化はわずかに高価ですが、確率が頻繁に変化しない場合、これは良いアプローチです。

*重複キーの場合、結合される代わりに最後の確率が使用されます(EnumeratedIntegerDistributionの動作とは少し異なります)

public class RandomGen5 extends BaseRandomGen {

    private int[] t_array = new int[4];
    private int sumOfNumerator;
    private final static int DENOM = (int) Math.pow(2, 24);
    private static final int[] bitCount = new int[] {18, 12, 6, 0};
    private static final int[] cumPow64 = new int[] {
            (int) ( Math.pow( 64, 3 ) + Math.pow( 64, 2 ) + Math.pow( 64, 1 ) + Math.pow( 64, 0 ) ),
            (int) ( Math.pow( 64, 2 ) + Math.pow( 64, 1 ) + Math.pow( 64, 0 ) ),
            (int) ( Math.pow( 64, 1 ) + Math.pow( 64, 0 ) ),
            (int) ( Math.pow( 64, 0 ) )
    };


    ArrayList[] base64Table = {new ArrayList<Integer>()
            , new ArrayList<Integer>()
            , new ArrayList<Integer>()
            , new ArrayList<Integer>()};

    public int nextNum() {
        int Rand = (int) (randGen.nextFloat() * sumOfNumerator);

        for ( int x = 0 ; x < 4 ; x ++ ) {
                if (Rand < t_array[x])
                    return x == 0 ? (int) base64Table[x].get(Rand >> bitCount[x])
                            : (int) base64Table[x].get( ( Rand - t_array[x-1] ) >> bitCount[x]) ;
        }
        return 0;
    }

    public void setIntProbList( int[] intList, float[] probList ) {
        Map<Integer, Float> map = normalizeMap( intList, probList );
        populateBase64Table( map );
    }

    private void clearBase64Table() {
        for ( int x = 0 ; x < 4 ; x++ ) {
            base64Table[x].clear();
        }
    }

    private void populateBase64Table( Map<Integer, Float> intProbMap ) {
        int startPow, decodedFreq, table_index;
        float rem;

        clearBase64Table();

        for ( Map.Entry<Integer, Float> numObj : intProbMap.entrySet() ) {
            rem = numObj.getValue();
            table_index = 3;
            for ( int x = 0 ; x < 4 ; x++ ) {
                decodedFreq = (int) (rem % 64);
                rem /= 64;
                for ( int y = 0 ; y < decodedFreq ; y ++ ) {
                    base64Table[table_index].add( numObj.getKey() );
                }
                table_index--;
            }
        }

        startPow = 3;
        for ( int x = 0 ; x < 4 ; x++ ) {
            t_array[x] = x == 0 ? (int) ( Math.pow( 64, startPow-- ) * base64Table[x].size() )
                    : ( (int) ( ( Math.pow( 64, startPow-- ) * base64Table[x].size() ) + t_array[x-1] ) );
        }

    }

    private Map<Integer, Float> normalizeMap( int[] intList, float[] probList ) {
        Map<Integer, Float> tmpMap = new HashMap<>();
        Float mappedFloat;
        int numerator;
        float normalizedProb, distSum = 0;

        //Remove duplicates, and calculate the sum of non-repeated keys
        for ( int x = 0 ; x < probList.length ; x++ ) {
            mappedFloat = tmpMap.get( intList[x] );
            if ( mappedFloat != null ) {
                distSum -= mappedFloat;
            } else {
                distSum += probList[x];
            }
            tmpMap.put( intList[x], probList[x] );
        }

        //Normalise the map to key -> corresponding numerator by multiplying with 2^24
        sumOfNumerator = 0;
        for ( Map.Entry<Integer, Float> intProb : tmpMap.entrySet() ) {
            normalizedProb = intProb.getValue() / distSum;
            numerator = (int) ( normalizedProb * DENOM );
            intProb.setValue( (float) numerator );
            sumOfNumerator += numerator;
        }

        return tmpMap;
    }
}
0
E.R.Tan

コードに新しいライブラリを追加することに反対していない場合、この機能は MockNeat で既に実装されています。 probabilities() メソッドを確認してください。

ウィキから直接のいくつかの例:

String s = mockNeat.probabilites(String.class)
                .add(0.1, "A") // 10% chance
                .add(0.2, "B") // 20% chance
                .add(0.5, "C") // 50% chance
                .add(0.2, "D") // 20% chance
                .val();

または、特定の確率で特定の範囲内の数値を生成する場合は、次のようなことができます。

Integer x = m.probabilites(Integer.class)
             .add(0.2, m.ints().range(0, 100))
             .add(0.5, m.ints().range(100, 200))
             .add(0.3, m.ints().range(200, 300))
             .val();

免責事項:私は図書館の著者であるため、推奨するときに偏見があるかもしれません。

0
Andrei Ciobanu

また、ここで応答しました: ランダムな国を見つけますが、人口の多い国を選ぶ確率は高くなければなりません 。 TreeMapの使用:

TreeMap<Integer, Integer> map = new TreeMap<>();
map.put(percent1, 1);
map.put(percent1 + percent2, 2);
// ...

int random = (new Random()).nextInt(100);
int result = map.ceilingEntry(random).getValue();
0
RoberMP