带概率的随机数
Posted
技术标签:
【中文标题】带概率的随机数【英文标题】:Random number with Probabilities 【发布时间】:2013-12-18 03:23:14 【问题描述】:我想知道在特定范围内生成随机数的最佳方法是什么(例如在 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。 你会说什么?
【问题讨论】:
我认为这样做是一个聪明的方法,但我不知道是否有什么“更好”的。只要确保你从 0 到 99,否则你最终会得到 101 个数字,而不是你想要的百分比。 是的,这似乎是合理的,否则你可以使用EnumeratedIntegerDistribution,示例显示here 当然,我在SSJ 中找不到针对您的问题的相关实现,但您应该比我更彻底地查看它... 【参考方案1】:您已经在问题中编写了实现。 ;)
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];
但只有当这是一个性能瓶颈并且每秒调用数百次时才应该使用它。
【讨论】:
您的回答对我帮助很大。非常感谢。【参考方案2】:你的方法已经很不错了,适用于任何范围。
只是想:另一种可能性是通过乘以一个常数乘数来去除分数,然后用这个乘数的大小构建一个数组。乘以 10 得到
P(1) = 2
P(2) = 3
P(3) = 5
然后您创建一个具有相反值的数组——“1”进入元素 1 和 2,“2”进入 3 到 6,依此类推:
P = (1,1, 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。如果不是,则将总数乘以相同的因子(这也将是您的数组长度)。
【讨论】:
非常感谢您对这个问题的回答 - 非常感谢您的帮助。【参考方案3】:如果你有性能问题而不是搜索所有 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
如果你有很多权重元素,你只需要平均访问 log2(weights.length)。
【讨论】:
【参考方案4】:前段时间我写了一个帮助类来解决这个问题。源代码应该足够清楚地显示概念:
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
【讨论】:
我喜欢这样!如果你想大规模使用它,hashmap 应该使用Float
而不是Double
以减少不必要的开销
你能解释一下main()
中的for循环吗?我不明白它在做什么。另外,为什么在计算之前不检查distSum
是否为1
?
你在用这个做什么:if (this.distribution.get(value) != null) distSum -= this.distribution.get(value);
?
@user366312 如果addNumber(int value, ...)
被同一个value
调用多次,则此行可确保总和distSum
保持正确的值。【参考方案5】:
您的方法适用于您选择的特定数字,尽管您可以通过使用 10 的数组而不是 100 的数组来减少存储空间。但是,这种方法不能很好地推广到大量结果或具有概率的结果如1/e
或1/PI
。
一个可能更好的解决方案是使用alias table。别名方法需要 O(n)
工作来为 n
结果设置表,但是无论有多少结果,生成的时间都是恒定的。
【讨论】:
非常感谢 :) 你帮了我很多。【参考方案6】:在参考了另一个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;
【讨论】:
【参考方案7】:试试这个: 在此示例中,我使用了一个字符数组,但您可以将其替换为整数数组。
权重列表包含每个字符的相关概率。 它代表我的字符集的概率分布。
在每个字符的权重列表中,我存储了他的实际概率加上任何先行概率的总和。
例如在weightsum中,'C'对应的第三个元素是65: P('A') + P('B) + P('C') = P(X=>c) 10 + 20 + 25 = 65
所以 weightsum 代表我的字符集的累积分布。 weightsum 包含以下值:
很容易看出,第8个元素对应H,有更大的差距(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 个随机字符,每个字符都根据累积概率绘制。
在 k i 中存储了一个从 0 到 weightsum 中分配的最大值的随机数。 然后我在 weightsum 中查找一个大于 k 的数字,该数字在 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));
代码给出了字符序列:
HHFAIIDFBDDDHFICJHACCDFJBGBHHB
让我们算一下吧!
A = 2 B = 4 C = 3 D = 5 E = 0 F = 4 G = 1 H = 6 我 = 3 J = 2
总计:30 正如我们希望的那样,D 和 H 的出现次数更多(70% 和 80% 概率。) 否则E根本没有出来。 (10% 的可能性)
【讨论】:
【参考方案8】:如果您不反对在代码中添加新库,则此功能已在 MockNeat 中实现,请检查 probabilities() 方法。
直接来自 wiki 的一些示例:
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();
免责声明:我是该库的作者,所以我在推荐它时可能会有偏见。
【讨论】:
【参考方案9】:这里是python代码,虽然你要求java,但是很相似。
# 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)
【讨论】:
【参考方案10】:也在此处回复:find random country but probability of picking higher population country should be higher。使用 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();
【讨论】:
【参考方案11】:这可能对某人有用,我在 python 中做的一个简单的。你只需要改变写 p 和 r 的方式。例如,这个将 0 到 0.1 之间的随机值投影到 1e-20 到 1e-12。
import random
def generate_distributed_random():
p = [1e-20, 1e-12, 1e-10, 1e-08, 1e-04, 1e-02, 1]
r = [0, 0.1, 0.3, 0.5, 0.7, 0.9, 1]
val = random.random()
for i in range(1, len(r)):
if val <= r[i] and val >= r[i - 1]:
slope = (p[i] - p[i - 1])/(r[i] - r[i - 1])
return p[i - 1] + (val - r[i - 1])*slope
print(generate_distributed_random())
【讨论】:
【参考方案12】:有一种更有效的方法,而不是进入分数或创建大数组或硬编码范围到 100
在您的情况下,数组变为 int[]2,3,5 sum = 10 只需将所有概率的总和运行随机数生成器就可以了 结果 = New Random().nextInt(10)
从索引 0 开始遍历数组元素并计算 sum 并在 sum 大于该索引的返回元素时返回作为输出
即如果结果是 6,那么它将返回索引 2,它不是 5
无论有大数字或范围大小,此解决方案都会扩展
【讨论】:
以上是关于带概率的随机数的主要内容,如果未能解决你的问题,请参考以下文章