三角形阵列中的Java偏置随机数

时间:2013-04-02 03:59:00

标签: java math random probability

此问题是Java- Math.random(): Selecting an element of a 13 by 13 triangular array的扩展。我随机选择两个数字(0-12包含),我希望值相等。

但是现在,由于这是一个乘法游戏,我想要一种偏向结果的方法,以便更频繁地出现某些组合(例如,如果玩家在12x8中做得更差,我希望它更频繁地出现)。最后,我想偏向于91种组合中的任何一种,但是一旦我解决这个问题,这应该不会很难。

我的想法:在三角形数字和int n上添加一些Random.nextInt(91 + n),将结果偏向一个组合。

private int[] triLessThan(int x, int[] bias) { // I'm thinking a 91 element array, 0 for no bias, positive for bias towards
    int i = 0;
    int last = 0;
    while (true) {
                    int sum = 0;
                    for (int a = 0; a < i * (i + 2)/2; a++){
                        sum += bias[a]
                    }
        int triangle = i * (i + 1) / 2;
        if (triangle + sum > x){
            int[] toReturn = {last,i};
            return toReturn;
        }
        last = triangle;
        i++;
    }
}

随机数卷:

int sum = sumOfArray(bias); // bias is the array;
int roll = random.nextInt(91 + sum);
int[] triNum = triLessThan(roll);
int num1 = triNum[1];
int num2 = roll - triNum[0]; //now split into parts and make bias[] add chances to one number.

其中sumOfArray只找到总和(该公式很容易)。这有用吗?

修改:使用Floris的想法:

随机数字卷:

int[] bias = {1,1,1,...,1,1,1} // 91 elements
int roll = random.nextInt(sumOfBias());
int num1 = roll;
int num2 = 0;
while (roll > 0){
    roll -= bias[num2];
    num2++;
}
num1 = (int) (Math.sqrt(8 * num2 + 1) - 1)/2;
num2 -= num1 * (num1 + 1) / 2;

1 个答案:

答案 0 :(得分:3)

您已经知道如何转换0到91之间的数字并将其转换为滚动(从您之前的问题的答案)。我建议你创建一个N元素的数组,其中N&gt;&gt; 91.用0 ... 90填充前91个元素,并将计数器A设置为91.现在选择0到A之间的数字,从数组中选择相应的元素,然后转换为乘法问题。如果答案错误,请将问题编号附加到数组的末尾,并将A加1。

这将创建一个数组,其中采样频率将表示问题被错误解决的次数 - 但如果问题在下次被问到时正确解决,则不会再次降低频率。 / p>

另一种更好的解决方案,一种更接近你的解决方案(但不同)会创建一个包含91个频率的数组 - 每个频率最初设置为1 - 并跟踪总和(最初为91)。但是现在,当你选择一个随机数(介于0和总和之间)时,你遍历数组,直到累积和大于你的随机数 - bin的数量就是你选择的掷骰,你用之前派生的公式转换它。如果答案是错误的,你可以增加垃圾箱并更新总和;如果它是正确的,则减少总和但从不减小到小于1的值,并更新总和。重复。

这应该可以准确地给出你所要求的内容:给定一个包含91个数字(“bins”)的数组,随机选择一个bin,使得bin的概率与其中的值成比例。返回bin的索引(可以使用之前的方法将其转换为数字组合)。调用此函数时,bin(频率)数组作为第一个参数,累积和作为第二个参数。你查找前n个元素的累积和首先超过按频率总和缩放的随机数:

private int chooseBin(float[] freq, float fsum) {
// given an array of frequencies (probabilities) freq
// and the sum of this array, fsum
// choose a random number between 0 and 90
// such that if this function is called many times
// the frequency with which each value is observed converges
// on the frequencies in freq
    float x, cs=0; // x stores random value, cs is cumulative sum
    int ii=-1;     // variable that increments until random value is found

    x = Math.rand();

    while(cs < x*fsum && ii<90) { 
    // increment cumulative sum until it's bigger than fraction x of sum
        ii++;
        cs += freq[ii];
    }
return ii;
}

我确认它给了我一个直方图(蓝色条),看起来就像我喂它的概率分布(红线): histogram

(注意 - 这是用matlab绘制的,因此X从1到91,而不是从0到90)。

这是另一个想法(这不是真正回答这个问题,但它可能更有趣):

您可以通过采样除均匀分布之外的其他内容来扭曲选择特定问题的概率。例如,均匀采样的随机变量的平方将有利于较小的数字。这给了我们一个有趣的可能性:

首先,将您的91号码随机顺序洗牌

接下来,从非均匀分布中选择一个数字(一个有利于较小数字的分布)。由于这些数字是随机改组的,实际上它们同样可能被选中。但现在就是诀窍:如果问题(由所选数字表示)得到正确解决,则将问题编号“移到堆栈顶部”,最不可能再次选择。如果玩家弄错了,它会被移动到堆栈的底部,最有可能再次被选中。随着时间的推移,困难的问题会移到堆栈的底部。

您可以使用

的变体创建具有不同偏斜度的随机分布
roll = (int)(91*(asin(Math.rand()*a)/asin(a)))

当你使a接近1时,该函数倾向于支持较低的数字,而数字的概率几乎为零:

non uniform sampling example

我相信以下代码部分执行我所描述的内容:

private int[] chooseProblem(float bias, int[] currentShuffle) { 
// if bias == 0, we choose from uniform distribution
// for 0 < bias <= 1, we choose from increasingly biased distribution
// for bias > 1, we choose from uniform distribution
// array currentShuffle contains the numbers 0..90, initially in shuffled order
// when a problem is solved correctly it is moved to the top of the pile
// when it is wrong, it is moved to the bottom.
// return value contains number1, number2, and the current position of the problem in the list
    int problem, problemIndex;

    if(bias < 0 || bias > 1) bias = 0;

    if(bias == 0) {
        problem = random.nextInt(91);
        problemIndex = problem;
    }
    else {
        float x = asin(Math.random()*bias)/asin(bias);
        problemIndex = Math.floor(91*x);
        problem = currentShuffle[problemIndex];
    }

    // now convert "problem number" into two numbers:
    int first, last;    
    first = (int)((Math.sqrt(8*problem + 1)-1)/2);
    last = problem - first * (first+1) / 2;

    // and return the result:
    return {first, last, problemIndex};
}


private void shuffleProblems(int[] currentShuffle, int upDown) {
// when upDown==0, return a randomly shuffled array
// when upDown < 0, (wrong answer) move element[-upDown] to zero
// when upDown > 0, (correct answer) move element[upDown] to last position
// note - if problem 0 is answered incorrectly, don't call this routine!

    int ii, temp, swap;

    if(upDown == 0) {

        // first an ordered list:
        for(ii=0;ii<91;ii++) {
            currentShuffle[ii]=ii;
        }

        // now shuffle it:
        for(ii=0;ii<91;ii++) {
            temp = currentShuffle[ii];
            swap = ii + random.nextInt(91-ii);
            currentShuffle[ii]=currentShuffle[swap];
            currentShuffle[swap]=temp;
        }
        return;
    }

    if(upDown < 0) {
        temp = currentShuffle[-upDown];
        for(ii = -upDown; ii>0; ii--) {
            currentShuffle[ii]=currentShuffle[ii-1];
        }
        currentShuffle[0] = temp;
    }
    else {
        temp = currentShuffle[upDown];
        for(ii = upDown; ii<90; ii++) { 
            currentShuffle[ii]=currentShuffle[ii+1];
        }
        currentShuffle[90] = temp;
    }
    return;
}


// main problem posing loop:

int[] currentShuffle = new int[91];
int[] newProblem;
int keepGoing = 1;

// initial shuffle:
shuffleProblems( currentShuffle, 0); // initial shuffle

while(keepGoing) {
    newProblem = chooseProblem(bias, currentShuffle);
    // pose the problem, get the answer
    if(wrong) {
        if(newProblem > 0) shuffleProblems( currentShuffle, -newProblem[2]);
    }
    else shuffleProblems( currentShuffle, newProblem[2]);
    // decide if you keep going...
}