私は、各番号に特定の確率が発生するかどうかにかかわらず、特定の範囲内で乱数を生成する最良の方法(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を取得しました。
あなたは何と言うでしょう?
あなたのものはすでにかなり良い方法であり、あらゆる範囲でうまく機能します。
考えてみてください:別の可能性は、定数乗数で乗算することで分数を取り除き、この乗数のサイズで配列を構築することです。あなたが得る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になることに基づいています。そうでない場合は、合計にこの同じ係数を掛けます(これが配列の長さにもなります)。
少し前に、この問題を解決するためのヘルパークラスを作成しました。ソースコードは、概念を十分に明確に示す必要があります。
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}
あなたはすでにあなたの質問で実装を書きました。 ;)
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秒間に数百回呼び出される場合にのみ使用してください。
すべての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)にアクセスする必要があります。
100の配列の代わりに10の配列を使用することでストレージを削減できますが、選択した特定の数値に対してアプローチは適切です。ただし、このアプローチは、多数の結果または1/e
などの確率を持つ結果にうまく一般化できませんまたは1/PI
。
潜在的に優れたソリューションは、 エイリアステーブル を使用することです。エイリアスメソッドは、n
の結果のテーブルを設定するためにO(n)
の作業を取りますが、結果の数に関係なく生成するのに一定の時間です。
これを試してください:この例では、文字の配列を使用しますが、整数配列で置き換えることができます。
重みリストには、各文字に関連する確率が含まれています。それは私の文字セットの確率分布を表します。
各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%の確率)
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)
別の 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;
}
}
コードに新しいライブラリを追加することに反対していない場合、この機能は 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();
免責事項:私は図書館の著者であるため、推奨するときに偏見があるかもしれません。
また、ここで応答しました: ランダムな国を見つけますが、人口の多い国を選ぶ確率は高くなければなりません 。 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();