[英]Generate an array of random integers with non-uniform distribution
我想編寫Java代碼以產生范圍為[1,4]的隨機整數數組。 數組的長度為N,在運行時提供。 問題是范圍[1,4]分布不均勻:
這意味着,如果我創建N = 100的數組,則數字“ 1”將平均出現在數組中40次,數字“ 2”出現10次,依此類推。
現在,我正在使用此代碼來生成范圍為[1,4]的均勻分布的隨機數:
public static void main(String[] args)
{
int N;
System.out.println();
System.out.print("Enter an integer number: ");
N = input.nextInt();
int[] a = new int[N];
Random generator = new Random();
for(int i = 0; i < a.length; i++)
{
a[i] = generator.nextInt(4)+1;
}
}
如何使用上圖所示的非均勻分布來實現它?
從您的代碼開始,這是一種實現方法:
public static void main(String[] args){
int N;
System.out.println();
System.out.print("Enter an integer number: ");
N = input.nextInt();
int[] a = new int[N];
Random generator = new Random();
for (int i = 0; i < a.length; i++) {
float n = generator.nextFloat();
if (n <= 0.4) {
a[i] = 1;
} else if (n <= 0.7) {
a[i] = 3;
} else if (n <= 0.9) {
a[i] = 4;
} else {
a[i] = 2;
}
}
}
更新:根據@pjs的建議,以降序排列順序選擇數字,因此您傾向於更早退出if塊
另一個簡單的解決方案是使用nextDouble()在[0,1)中生成一個隨機雙精度數。 如果值<.4,請選擇1,否則,如果<(.4 + .2),請選擇2,依此類推,最后一個分支始終選擇最后一個選擇。 使用for循環很容易將其概括。
對於更通用的方法,可以使用分布概率填充NavigableMap
:
double[] probs = {0.4, 0.1, 0.2, 0.3};
NavigableMap<Double, Integer> distribution = new TreeMap<Double, Integer>();
for(double p : probs) {
distribution.put(distribution.isEmpty() ? p : distribution.lastKey() + p, distribution.size() + 1);
}
然后使用均勻分布的[0,1>]范圍內的隨機密鑰查詢地圖:
Random rnd = new Random();
for(int i=0; i<20; i++) {
System.out.println(distribution.ceilingEntry(rnd.nextDouble()).getValue());
}
這將使用以下鍵/值對填充地圖:
0.4 -> 1
0.5 -> 2
0.7 -> 3
1.0 -> 4
要查詢地圖,首先要生成一個范圍為0到1的均勻分布的double。使用ceilingEntry
方法查詢地圖並傳遞隨機數將返回“與大於或等於給定鍵的最小鍵關聯的映射”。 ,因此,例如,傳遞范圍<0.4,0.5]中的值將返回映射0.5 -> 2
的條目。 因此,在返回的地圖條目上使用getValue()
將返回2。
令a1, a2, a3
和a4
為指定相對概率的雙精度數,並且s = a1+a2+a3+a4
這意味着1
的概率為a1/s
, 2
的概率為a2/s
,...
然后使用generator.nextDouble()
創建一個隨機double d。
如果0 <= d < a1/s
則整數應為1
如果a1/s <= d < (a1+a2)/s
則整數應為2
如果(a1+a2)/s <= d < (a1+a2+a3)/s
則整數應為3
如果(a1+a2+a3)/s <= d < 1
則整數應為4
Miquel的版本(以及Teresa的建議)的擴展性稍強:
double[] distro=new double[]{.4,.1,.3,.2};
int N;
System.out.println();
System.out.print("Enter an integer number: ");
Scanner input = new Scanner(System.in);
N = input.nextInt();
int[] a = new int[N];
Random generator = new Random();
outer:
for(int i = 0; i < a.length; i++)
{
double rand=generator.nextDouble();
double val=0;
for(int j=1;j<distro.length;j++){
val+=distro[j-1];
if(rand<val){
a[i]=j;
continue outer;
}
}
a[i]=distro.length;
}
對於您上面給出的特定問題,其他人提供的解決方案效果很好,而別名方法可能會顯得過大。 但是,您在評論中說,您實際上將在范圍更大的發行版中使用它。 在那種情況下,建立別名表的開銷對於實際生成值的O(1)行為可能是值得的。
這是Java的源代碼。 如果您不想使用Mersenne Twister,可以很容易地將其恢復為使用Java的Random
:
/*
* Created on Mar 12, 2007
* Feb 13, 2011: Updated to use Mersenne Twister - pjs
*/
package edu.nps.or.simutils;
import java.lang.IllegalArgumentException;
import java.text.DecimalFormat;
import java.util.Comparator;
import java.util.Stack;
import java.util.PriorityQueue;
import java.util.Random;
import net.goui.util.MTRandom;
public class AliasTable<V> {
private static Random r = new MTRandom();
private static DecimalFormat df2 = new DecimalFormat(" 0.00;-0.00");
private V[] primary;
private V[] alias;
private double[] primaryP;
private double[] primaryPgivenCol;
private static boolean notCloseEnough(double target, double value) {
return Math.abs(target - value) > 1E-10;
}
/**
* Constructs the AliasTable given the set of values
* and corresponding probabilities.
* @param value
* An array of the set of outcome values for the distribution.
* @param pOfValue
* An array of corresponding probabilities for each outcome.
* @throws IllegalArgumentException
* The values and probability arrays must be of the same length,
* the probabilities must all be positive, and they must sum to one.
*/
public AliasTable(V[] value, double[] pOfValue) {
super();
if (value.length != pOfValue.length) {
throw new IllegalArgumentException(
"Args to AliasTable must be vectors of the same length.");
}
double total = 0.0;
for (double d : pOfValue) {
if (d < 0) {
throw new
IllegalArgumentException("p_values must all be positive.");
}
total += d;
}
if (notCloseEnough(1.0, total)) {
throw new IllegalArgumentException("p_values must sum to 1.0");
}
// Done with the safety checks, now let's do the work...
// Cloning the values prevents people from changing outcomes
// after the fact.
primary = value.clone();
alias = value.clone();
primaryP = pOfValue.clone();
primaryPgivenCol = new double[primary.length];
for (int i = 0; i < primaryPgivenCol.length; ++i) {
primaryPgivenCol[i] = 1.0;
}
double equiProb = 1.0 / primary.length;
/*
* Internal classes are UGLY!!!!
* We're what you call experts. Don't try this at home!
*/
class pComparator implements Comparator<Integer> {
public int compare(Integer i1, Integer i2) {
return primaryP[i1] < primaryP[i2] ? -1 : 1;
}
}
PriorityQueue<Integer> deficitSet =
new PriorityQueue<Integer>(primary.length, new pComparator());
Stack<Integer> surplusSet = new Stack<Integer>();
// initial allocation of values to deficit/surplus sets
for (int i = 0; i < primary.length; ++i) {
if (notCloseEnough(equiProb, primaryP[i])) {
if (primaryP[i] < equiProb) {
deficitSet.add(i);
} else {
surplusSet.add(i);
}
}
}
/*
* Pull the largest deficit element from what remains. Grab as
* much probability as you need from a surplus element. Re-allocate
* the surplus element based on the amount of probability taken from
* it to the deficit, surplus, or completed set.
*
* Lather, rinse, repeat.
*/
while (!deficitSet.isEmpty()) {
int deficitColumn = deficitSet.poll();
int surplusColumn = surplusSet.pop();
primaryPgivenCol[deficitColumn] = primaryP[deficitColumn] / equiProb;
alias[deficitColumn] = primary[surplusColumn];
primaryP[surplusColumn] -= equiProb - primaryP[deficitColumn];
if (notCloseEnough(equiProb, primaryP[surplusColumn])) {
if (primaryP[surplusColumn] < equiProb) {
deficitSet.add(surplusColumn);
} else {
surplusSet.add(surplusColumn);
}
}
}
}
/**
* Generate a value from the input distribution. The alias table
* does this in O(1) time, regardless of the number of elements in
* the distribution.
* @return
* A value from the specified distribution.
*/
public V generate() {
int column = (int) (primary.length * r.nextDouble());
return r.nextDouble() <= primaryPgivenCol[column] ?
primary[column] : alias[column];
}
public void printAliasTable() {
System.err.println("Primary\t\tprimaryPgivenCol\tAlias");
for(int i = 0; i < primary.length; ++i) {
System.err.println(primary[i] + "\t\t\t"
+ df2.format(primaryPgivenCol[i]) + "\t\t" + alias[i]);
}
System.err.println();
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.