我需要一个函数来生成随机整数。 (现在假设Java long
类型,但稍后会扩展到BigInteger
或BitSet
。)
棘手的部分是有一个参数P,它指定结果中任何位的(独立)概率为1.
如果P = 0.5,那么我们可以使用标准随机数发生器。 P的一些其他值也易于实现。这是一个不完整的例子:
Random random = new Random();
// ...
long nextLong(float p) {
if (p == 0.0f) return 0L;
else if (p == 1.0f) return -1L;
else if (p == 0.5f) return random.nextLong();
else if (p == 0.25f) return nextLong(0.5f) & nextLong(0.5f);
else if (p == 0.75f) return nextLong(0.5f) | nextLong(0.5f);
else if (p == 0.375f) return nextLong(0.5f) & nextLong(0.75f); // etc
else {
// What goes here??
String message = String.format("P=%f not implemented yet!", p);
throw new IllegalArgumentException(message);
}
}
有没有办法可以将P的任何值在0.0和1.0之间进行推广?
答案 0 :(得分:4)
首先,你已经在代码中使用了一些丑陋的数学。
定义x和y分别是X = p(x = 1),Y = p(y = 1)的概率为1的位。 那我们就有了
p( x & y = 1) = X Y
p( x | y = 1) = 1 - (1-X) (1-Y)
p( x ^ y = 1) = X (1 - Y) + Y (1 - X)
现在,如果我们让Y = 1/2,我们得到
P( x & y ) = X/2
P( x | y ) = (X+1)/2
现在将RHS设置为我们想要的概率,我们有两种情况可以解决X
X = 2 p // if we use &
X = 2 p - 1 // if we use |
接下来我们假设我们可以再次使用它来获得另一个变量Z的X ... 然后我们继续迭代,直到我们完成“足够”。
有点不清楚,但考虑到p = 0.375
0.375 * 2 = 0.75 < 1.0 so our first operation is &
0.75 * 2 = 1.5 > 1.0 so our second operation is |
0.5 is something we know so we stop.
因此我们可以通过X1&amp;获得p = 0.375的变量。 (X2 | X3)
问题是对于大多数变量,这不会终止。 e.g。
0.333 *2 = 0.666 < 1.0 so our first operation is &
0.666 *2 = 1.333 > 1.0 so our second operation is |
0.333 *2 = 0.666 < 1.0 so our third operation is &
etc...
所以p = 0.333可以通过
生成X1 & ( X2 | (X3 & (X4 | ( ... ) ) ) )
现在我怀疑在系列中获取足够的术语会给你足够的准确性,这可以写成一个递归函数。然而,也许有更好的方式......我认为操作的顺序与p的二进制表示有关,我只是不确定如何......并且没有时间更深入地考虑它。
无论如何,这是一些未经测试的C ++代码。您应该能够轻松地进行java化。
uint bitsWithProbability( float p )
{
return bitsWithProbabilityHelper( p, 0.001, 0, 10 );
}
uint bitsWithProbabilityHelper( float p, float tol, int cur_depth, int max_depth )
{
uint X = randbits();
if( cur_depth >= max_depth) return X;
if( p<0.5-tol)
{
return X & bitsWithProbabilityHelper( 2*p, 0.001, cur_depth+1, max_depth );
}
if(p>0.5+tol)
{
return X | bitsWithProbabilityHelper( 2*p-1, 0.001, cur_depth+1, max_depth );
}
return X;
}
答案 1 :(得分:2)
通过数字分配比例位数。 伪代码:
long generateNumber( double probability ){
int bitCount = 64 * probability;
byte[] data = new byte[64]; // 0-filled
long indexes = getRandomLong();
for 0 to bitCount-1 {
do {
// distribute this bit to some postition with 0.
int index = indexes & 64;
indexes >> 6;
if( indexes == 0 ) indexes = getRandomLong();
} while ( data[index] == 0 );
data[index] = 1;
}
return bytesToLong( data );
}
我希望你明白我的意思。也许byte[]
可以替换为long
和位操作以使其更快。
答案 2 :(得分:1)
使用随机生成器生成0到1之间的统一浮点数r。如果r> p,则将该位设置为0,否则将其设置为1
答案 3 :(得分:1)
如果你想要在概率为P的情况下应用一些分布,你得到1并且概率为1-P你在任何特定位得到0你最好的选择就是单独生成每个位,概率P为a 1(听起来像一个递归的定义,我知道)。
这是一个解决方案,我将在下面介绍:
public class MyRandomBitGenerator
{
Random pgen = new Random();
// assumed p is well conditioned (0 < p < 1)
public boolean nextBitIsOne(double p){
return pgen.nextDouble() < p ? true : false;
}
// assumed p is well conditioned (0 < p < 1)
public long nextLong(double p){
long nxt = 0;
for(int i = 0; i < 64; i++){
if(nextBitIsOne(p)){
nxt += 1 << i;
}
}
return nxt;
}
}
基本上,我们首先确定如何使用概率P生成值1:pgen.nextDouble()
通过询问是否小于p
我们正在采样,生成一个介于0和1之间且具有均匀概率的数字这个分布使得我们期望看到p
1s,因为我们无限地调用这个函数。
答案 4 :(得分:1)
以下是我最终解决的问题。
部分灵感来自OndraŽižka的答案。
好处是它减少了每64位输出调用Random.nextLong()
到8次调用的次数。
为了比较,每个位的滚动需要64次调用。按位AND / OR使用2到32个调用,具体取决于P
当然,计算二项式概率同样昂贵,所以那些进入另一个查找表。
这是很多代码,但它在性能方面取得了成效。
更新 - 将其与按位AND / OR解决方案合并。它现在使用该方法,如果它猜测它将更有效(就调用Random.next()
而言。)
答案 5 :(得分:1)
为了避免递归,我们从右到左迭代地处理P的位,而不是从左到右递归地处理。这在浮点表示中会很棘手,所以我们从二进制表示中提取指数/尾数字段。
class BitsWithProbabilityHelper {
public BitsWithProbabilityHelper(float prob, Random rnd) {
if (Float.isNaN(prob)) throw new IllegalArgumentException();
this.rnd = rnd;
if (prob <= 0f) {
zero = true;
return;
}
// Decode IEEE float
int probBits = Float.floatToIntBits(prob);
mantissa = probBits & 0x7FFFFF;
exponent = probBits >>> 23;
// Restore the implicit leading 1 (except for denormals)
if (exponent > 0) mantissa |= 0x800000;
exponent -= 150;
// Force mantissa to be odd
int ntz = Integer.numberOfTrailingZeros(mantissa);
mantissa >>= ntz;
exponent += ntz;
}
/** Determine how many random words we need from the system RNG to
* generate one output word with probability P.
**/
public int iterationCount() {
return - exponent;
}
/** Generate a random number with the desired probability */
public long nextLong() {
if (zero) return 0L;
long acc = -1L;
int shiftReg = mantissa - 1;
for (int bit = exponent; bit < 0; ++ bit) {
if ((shiftReg & 1) == 0) {
acc &= rnd.nextLong();
} else {
acc |= rnd.nextLong();
}
shiftReg >>= 1;
}
return acc;
}
/** Value of <code>prob</code>, represented as m * 2**e where m is always odd. */
private int exponent;
private int mantissa;
/** Random data source */
private final Random rnd;
/** Zero flag (special case) */
private boolean zero;
}
答案 6 :(得分:0)
假设位数组的大小为L.如果L = 1,则第1位为1的概率为P,0为1-P。对于L = 2,得到00的概率是(1-P) 2 ,01或10是P(1-P),11是P 2 。扩展这个逻辑,我们可以先通过比较随机数和P来确定第一位,然后缩放随机数,这样我们就可以再次获得0到1之间的任何值。示例javascript代码:
function getRandomBitArray(maxBits,probabilityOf1) {
var randomSeed = Math.random();
bitArray = new Array();
for(var currentBit=0;currentBit<maxBits;currentBit++){
if(randomSeed<probabilityOf1){
//fill 0 at current bit
bitArray.push(0);
//scale the sample space of the random no from [0,1)
//to [0.probabilityOf1)
randomSeed=randomSeed/probabilityOf1;
}
else{
//fill 1 at current bit
bitArray.push(1);
//scale the sample space to [probabilityOf1,1)
randomSeed = (randomSeed-probabilityOf1)/(1-probabilityOf1);
}
}
}
修改强> 该代码确实生成完全随机的位。我会尝试更好地解释算法。
每个位串都有一定的发生概率。假设一个字符串有发生概率 p ;如果我们的随机数下降是长度为p的某个区间,我们想要选择该字符串。区间的起点必须固定,但其值不会有太大差异。假设我们正确选择了高达k位。然后,对于下一位,我们将对应于此k长度位串的间隔分成两部分大小,比例为 P:1 - P (此处< em> P 是获得1)的概率。我们说如果随机数在第一部分中,则下一位将为1,如果在第二部分中则为0。这确保了长度为k + 1的字符串的概率也保持正确。
Java代码:
public ArrayList<Boolean> getRandomBitArray(int maxBits, double probabilityOf1) {
double randomSeed = Math.random();
ArrayList<Boolean> bitArray = new ArrayList<Boolean>();
for(int currentBit=0;currentBit<maxBits;currentBit++){
if(randomSeed<probabilityOf1){
//fill 0 at current bit
bitArray.add(false);
//scale the sample space of the random no from [0,1)
//to [0.probabilityOf1)
randomSeed=randomSeed/probabilityOf1;
}
else{
//fill 1 at current bit
bitArray.add(true);
//scale the sample space to [probabilityOf1,1)
randomSeed = (randomSeed-probabilityOf1)/(1-probabilityOf1);
}
}
return bitArray;
}