带概率的随机数

时间:2013-12-02 12:08:30

标签: java random probability

我想知道在每个数字有一定概率发生的特定范围内生成随机数的最佳方式(例如在Java中)是什么?

e.g。

使用以下概率从[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
你会说什么?

12 个答案:

答案 0 :(得分:32)

你已经是一个非常好的方式,适用于任何范围。

想一想:另一种可能性是通过乘以常数乘数来摆脱分数,然后用这个乘数的 size 构建一个数组。乘以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分之二 - 仍为0.1。

这是基于你的原始概率所有加起来为1.如果他们没有,则将总数乘以相同的因子(这也将是你的数组长度)。

答案 1 :(得分:26)

前段时间我写了一个帮助类来解决这个问题。源代码应该清楚地表明这个概念:

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}

答案 2 :(得分:8)

您已在问题中编写了实施内容。 ;)

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];

但是,只有当这是性能瓶颈并且每秒调用几百次时才应该使用它。

答案 3 :(得分:5)

如果您遇到性能问题而不是搜索所有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 :(得分:4)

您的方法适用于您选择的特定数字,尽管您可以通过使用10的数组而不是100的数组来减少存储。但是,这种方法并不能很好地概括为具有概率的大量结果或结果例如1/e1/PI

可能更好的解决方案是使用alias table。别名方法需要O(n)工作来设置n结果的表格,但是无论结果有多少,都需要生成恒定的时间。

答案 5 :(得分:1)

试试这个: 在这个例子中,我使用了一个chars数组,但你可以用整数数组替换它。

权重列表包含每个字符的相关概率。 它代表了我的charset的概率分布。

在每个char的weightum列表中,我存储了他的实际概率加上任何先行概率的总和。

例如,在权重中,对应于'C'的第三个元素是65:
P('A')+ P('B)+ P('C')= P(X =&gt; c)
10 + 20 + 25 = 65

所以weightsum代表我的charset的累积分布。 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个随机char提取,每个提取相应于累积概率。

在k中,我存储了一个从0到以权重分配的最大值的随机数。 然后我在weightum中查找一个数字而不是k,数字在权重中的位置对应于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
我= 3
J = 2

Total.:30
我们希望D和H有更多的出现(70%和80%概率) Otherwinse E根本没出来。 (10%概率)

答案 6 :(得分:0)

在参考另一个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 :(得分:0)

如果您不反对在代码中添加新库,则此功能已在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();

免责声明:我是该库的作者,因此当我推荐该库时可能会有所偏见。

答案 8 :(得分:0)

即使您要求使用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)

答案 9 :(得分:0)

也在这里回复: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();

答案 10 :(得分:0)

这可能对某人有用,这是我在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())

答案 11 :(得分:0)

还有一种比分散分数或创建大数组或硬编码范围到100更有效的方法

在您的情况下,数组变为int [] {2,3,5} sum = 10 只是将所有概率运行随机数生成器的总和 结果=新的Random()。nextInt(10)

对索引0的数组元素进行迭代,计算sum并在sum大于该索引的return元素时返回

即,如果结果为6,则它将返回不为5的索引2

此解决方案将不考虑规模或范围的大小而扩展