问题
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.
回答1:
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 n
th 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++;
}
}
http://ideone.com/vzQEBz
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;
}
http://ideone.com/1qBHfX
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;
That's it!
*assuming that the native StrictMath#sqrt(double) function is constant time - I'm actually not sure about this.
来源:https://stackoverflow.com/questions/15755228/java-math-random-selecting-an-element-of-a-13-by-13-triangular-array