Edit: This problem is solved. If you would like to help on another problem, please visit Java Biasing Random Numbers in a Triangular Array.
I'm doing a multiplication game, so I pick 2 numbers between 0 and 12 inclusive. If I do that like this:
int num1 = (int)(Math.random() * 13);
int num2 = (int)(Math.random() * 13);
the squares (0x0,1x1,2x2,etc) are picked half the time (because 1x2 is the same as 2x1). How can I make all combinations picked at the same frequency? There are 91 possible combinations (n(n+1)/2). If it helps, here is a 13 by 13 triangular array:
{{0},
{0,0},
{0,0,0},
{0,0,0,0},
{0,0,0,0,0},
{0,0,0,0,0,0},
{0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0}};
I've tried picking the first number and giving the second number a 50% chance of being the first one. This did not work. I tried giving the second number a 1/91 chance of being the first one. This resulted in the smaller numbers being picked a far greater number of times (around 7/91 of the time; it is a smooth, curved increase). I thought about having a single random number: int roll = random.next(91) and then splitting it into 2 entries (like a coordinate (x,y)) but I could not figure out how to split it.
解决方案
The int roll = random.next(91) strategy will work just fine. You get guaranteed, worry-free uniform distribution, and better performance to boot, since you're only picking 1 random number. You simply need to find a formula that identifies where one "row" ends and another begins. Look for the pattern:
0, 1, 3, 6, 10, 15, ...
There's a reason they're called "triangular numbers..."
Let's flesh this out a bit more. You want to actually find the nearest triangle number smaller than the random roll that you picked: that gets you to the right row, and the difference of that triangle number and roll gets you the offset into that row.
Given that the nth triangle number is given by n*(n+1)/2, how do you find the largest one smaller than roll? Given the small size of the array, a naïve implementation should be plenty fast:
int largestTriangleNumberSmallerThan(int x) {
int i = 0;
int last = 0;
while (true) {
int triangle = i*(i+1)/2;
if (triangle > x) return last;
last = triangle;
i++;
}
}
Of course, that's boring and didn't take any thought. We can do better! We can do it in constant* time, regardless of how big the input is! Start by inverting the function (we only care about the positive root, of course):
n = (Math.sqrt(8y + 1) - 1)/2
Then truncate the decimal part, and run it back through:
int largestTriangleNumberSmallerThan(int x) {
int n = (int) (Math.sqrt(8*x + 1) - 1)/2;
return n*(n+1)/2;
}
To put it all together:
int roll = random.nextInt(91);
int num1 = (int) (Math.sqrt(8*roll + 1) - 1)/2;
int num2 = roll - num1*(num1+1)/2;