I want to write Java code to produce an array of random integers in the range [1,4]. The array's length is N, which is provided at run time. The problem is that the range [1,4] is not uniformly distributed:
It means that if I create arrays with N=100, the number '1' will appear averagely 40 times in an array, number '2' 10 times, and so on.
For now I am using this code to generate uniform-distributed random numbers in range [1,4]:
public static void main(String[] args)
{
int N;
System.out.println();
System.out.print("Enter an integer number: ");
N = input.nextInt();
int[] a = new int[N];
Random generator = new Random();
for(int i = 0; i < a.length; i++)
{
a[i] = generator.nextInt(4)+1;
}
}
How do I implement it with a the non-uniform distribution as shown in the graph above?
a slightly more extensible version of Miquel's (and also what Teresa suggested):
Another easy solution is to use nextDouble() which generates a random double in [0,1). If the value is < .4 choose 1, else if it is < (.4 + .2) choose 2, etc, with the last branch always choosing the last choice. This is easily generalized using a for loop.
Here's a way to do it, starting from your code:
UPDATE: at @pjs' suggestion, select numbers in order of desdencing probability so you tend to exit the if block earlier
For the specific problem you gave above, the solutions provided by others work very well and the alias method would be overkill. However, you said in a comment that you were actually going to use this in a distribution with a much larger range. In that case, the overhead of setting up an alias table may be worthwhile to get the O(1) behavior for actually generating values.
Here's source in Java. It's easy to revert it back to using Java's stock
Random
if you don't want to grab Mersenne Twister:Let
a1, a2, a3
anda4
be doubles that specify the relative probabilities ands = a1+a2+a3+a4
That means the probability for1
isa1/s
, the probability for2
isa2/s
, ...Then create a random double d using
generator.nextDouble()
.If
0 <= d < a1/s
then the integer should be 1,if
a1/s <= d < (a1+a2)/s
then the integer should be 2if
(a1+a2)/s <= d < (a1+a2+a3)/s
then the integer should be 3if
(a1+a2+a3)/s <= d < 1
then the integer should be 4For a more generic approach, you can populate a
NavigableMap
with the distribution probability:and later query the map with a uniformly distributed random key in the range [0, 1>:
This will populate the map with the following key/value pairs:
To query the map, you first generate a uniformly distributed double in the range 0 to 1. Querying the map using the
ceilingEntry
method and passing the random number will return the "mapping associated with the least key greater than or equal to the given key", so e.g. passing a value in the range <0.4, 0.5] will return the entry with the mapping0.5 -> 2
. UsinggetValue()
on the returned map entry will hence return 2.