1

I have been trying to find a way to generate a number which is more likely to generate a number in the middle of a range. Given:

rnum = r.nextInt(5) + 1;//+ 1 to exclude 0

generates a completely random number between 1 and 5(0 and 4 if + 1 is removed). What I want to do, is generate less of 1 and 5, and generate a lot of 3's. I attempted this:

int[] classes = {1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 5, 5};
System.out.println("Class " +  classes[r.nextInt(15)]);//logs the output

But it generates this:(print statement repeated 10 times)

Class 2
Class 3
Class 1
Class 4
Class 3
Class 4
Class 2
Class 3
Class 2
Class 5

However, this is neither efficient or a good way to do it. Further, because the random number generator used in retrieving the number is completely random instead of focusing on a center value, thus making the output above. 3 only appears 30% of the time, which is too low. 2 appears 30% of the time as well, which means (in this test) it has the same probability to be generated as 3.

So, how can I generate a number randomly with a higher probability to generate a number in the middle of a range?

avysk
  • 1,973
  • 12
  • 18
Zoe
  • 27,060
  • 21
  • 118
  • 148
  • 6
    10 times is a much too small sample to deduce accurate probability and obtain the desired distribution. Your way of doing is fine. – JB Nizet Feb 25 '17 at 12:38
  • 1
    adding to wht @JBNizet said. add the print in a for loop and count the output programatically to calculate the percentage to have a better site. I suggest 1000 loop iterations. – hasan Feb 25 '17 at 12:40
  • 3
    You might want to consider a [binomial distribution](https://en.wikipedia.org/wiki/Binomial_distribution) for your random variable. – Monkey Supersonic Feb 25 '17 at 12:41
  • With a more repesentative sample: http://ideone.com/uFhzKA – JB Nizet Feb 25 '17 at 12:46
  • You have some misunderstanding here. Probability has nothing to do with frequency in *one* specific sample. Independent of how many entries in a sample you have (even though it's highly unlikely) there's still possibility that the sample will consist, say, only of 1s. – avysk Feb 25 '17 at 12:48
  • what if we replaced the (5) with random from 1 to 5 and the +1 with random 1 to 5. I guess that will achieve our purpose 100%. – hasan Feb 25 '17 at 12:49
  • Some real-life illustration: even though the probability of getting heads is 1/2, and probability of getting tails is 1/2, it's still possible that you do 10 coin throws and will get only heads. – avysk Feb 25 '17 at 12:49
  • avysk: it is always a possibility to get all 1s, but the chance of generating it is lower when the generation method aims to generate middle values. The chance of getting all 1s is there, but much lower than by just generating a number. hasan83: printing out 1000 loops 10 times and calculating average every time gave this: Average: 3 Average: 2 Average: 2 Average: 3 Average: 2 Average: 2 Average: 2 Average: 3 Average: 2 Average: 3 – Zoe Feb 25 '17 at 12:50
  • @hasan83 it surely will do the bias towards the middle values, but we don't know what's the desired distribution here... – avysk Feb 25 '17 at 12:50
  • @LunarWatcher Thanks, I know :D I'm just saying that the statement in the original question ("it does not seem to work as it produced the same amount of 2s as of 3s") is incorrect. – avysk Feb 25 '17 at 12:52
  • @avysk what do you mean desired distribution? I think the question exactly want a bias towards mid. – hasan Feb 25 '17 at 12:52
  • The desired distribution is towards the middle of the range, in this case 3. The way I did will not work as a general solution, and if the range is 1-1000 it will require a lot of manual number input – Zoe Feb 25 '17 at 12:53
  • You can automate the array generation, no need to manually input 1000s of values. – Marvin Feb 25 '17 at 12:53
  • @hasan83 the desired distribution means "how much more 3s you want to get". The task to get 3s 90% of the time is different from the task to get 3s 50% of the time. (And it applies not only to 3s). – avysk Feb 25 '17 at 12:54
  • my suggested solution doesnt include the predefind array. – hasan Feb 25 '17 at 12:55
  • got you now. true. what do you think the percentages of my proposed solution? – hasan Feb 25 '17 at 12:56
  • Getting 3s about 50-60% of the time, and giving a decreasing amount to the rest of the values(e.g. 2 and 4 will have 15-20% each and 1 and 5 10% each) – Zoe Feb 25 '17 at 13:00
  • Added an answer below where you can specify probability for every number you want to get. – avysk Feb 25 '17 at 13:09

4 Answers4

3

The easiest approach would be to start with an array which contains desired probabilities.

import java.util.Random;

class Main {

    public static int getOneNumber(double[] probs, Random rnd) {
        double r = rnd.nextDouble();
        for (int j = 0; j < probs.length; j++) {
            if (r < probs[j]) {
                return j;
            }
            r -= probs[j];
        }
        throw new RuntimeException("probabilities should sum to 1");
    }


    public static void main(String[] args) {

        // Desired probabilities
        double[] probabilities = {
            0.05, // 0
            0.15, // 1
            0.6, // 2
            0.15, // 3
            0.05 }; // 4

        Random rnd = new Random();
        for (int i = 0; i < 20; i++) {
            System.out.println(getOneNumber(probabilities, rnd));
        }
    }
}

The idea here is as following. We generate random number between 0 and 1. Now we check: is it below 0.05? If it is, we return 0 -- and it will happen with the probability 0.05. If it is not, we check if our number is between 0.05 and 0.15 (by subtracting 0.05 from it and comparing with 0.1). If it is (this happens with probability 0.15-0.05 = 0.1) -- we got 1. If it is not, we check if the random number is between 0.15 and 0.75, etc.

Zoe
  • 27,060
  • 21
  • 118
  • 148
avysk
  • 1,973
  • 12
  • 18
1

The easiest approach is that one:

Generate a floating point number between 0 and 1. Double the value and subtract 1 Take that number to any power, e.g. 10. Divide it by 2 and add 0.5

Multiply your result with 15

float fVal = Math.power(r.next()*2-1, 10) / 2 + 0.5;
int iVal = Math.floor(fVal * 15);

int[] classes = {1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 5, 5};
System.out.println("Class " +  classes[iVal]);//logs the output

This should make your probability look more like a gaussian bell curve Maybe you want to read about Normal Distribution

Psi
  • 6,387
  • 3
  • 16
  • 26
  • 2
    Pretty sure Java Random has `nextGaussian()` that returns a "double value, chosen from (approximately) the usual normal distribution with mean 0.0 and standard deviation 1.0" – tnt Feb 25 '17 at 12:59
  • Possible, but that won't lead to understanding on what he tries to accomplish. – Psi Feb 25 '17 at 13:00
  • r.next() as described here requires input. Math.power is an unrecognized method suggesting Math.pow instead, which requires double input – Zoe Feb 25 '17 at 13:07
0

You can generate the random values using binomial distribution with an algorithm presented in this answer:

public static int getBinomial(int n, double p) {
  int x = 0;
  for(int i = 0; i < n; i++) {
    if(Math.random() < p)
      x++;
  }
  return x;
}

Call the function like this to get a peak of the value 3 in the middle:

getBinomial(4,0.5) +1;

The values will be approximately distributed like follows:

 1     2    3    4    5
1/16  1/4  3/8  1/4  1/16
Community
  • 1
  • 1
Calculator
  • 2,769
  • 1
  • 13
  • 18
0

Start with a random number in [0, 1] and then raise it to the power of some positive number. Powers < 1 will bias upward, i.e. the numbers will be more likely to be higher than lower within [0, 1], and powers > 1 will bias downward. Then use multiplication and addition to shift the range of numbers from [0, 1] to your desired range.

double rnum,bias_factor;
int low,high;
//low, high and bias_factor are the inputs, then :
high = (low + high)/2;
for(int i = 0;i<=10;i++)
    {
        rnum = Math.random();
        rnum = Math.pow(rnum,bias_factor);
        rnum = (low + (high - low)*rnum)+1;
        System.out.println((int)rnum);
    }

The statement:

high=(low + high)/2; 

makes the middle value of given range as the upper value of the range. Hence with a low bias factor the output gets biased towards the upper values of a range. For eg: Initially giving the inputs as high = 5, low = 1; and bias_factor = 0.4 generates more middle values(3's) in the range [1,5]. I think this might help: https://gamedev.stackexchange.com/questions/54551/using-random-numbers-with-a-bias

Community
  • 1
  • 1
MRJ
  • 9
  • 2