Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decreasing chance of choosing a number from a list of consecutive numbers

Say for example I am given the number 3. I then have to choose a random number from 0 to 3, but where 0 has a bigger chance of being chosen than 1, 1 has a bigger chance of being chosen than 2, and 2 has a bigger chance of being chosen than 3.

I already know that a percentage chance of choosing a specific number from 0 to 3 can kind of be achieved by doing the following:

double r = Math.random();
int n = 0;
if (r < 0.5) {
    n = 0;
    // 50% chance of being 0
} else if (r < 0.8) {
    n = 1;
    // 30% chance of being 1
} else if (r < 0.95) {
    n = 2;
    // 15% chance of being 2
} else {
    n = 3;
    // 5% chance of being 3
}

The problem is that the 3 can be anything. How can I do this?

Note: The numbers 0.5, 0.8 and 0.95 were arbitrarily chosen by me. I would expect those numbers to decrease so that the sum of all of them equals 1, and so that none of them are the same, if that is possible in some way.

like image 637
Jacques Marais Avatar asked Jul 13 '17 14:07

Jacques Marais


2 Answers

This seems like you would want to work with a generic probability distribution whose domain can be scaled to your liking. You could chose any function such that f(0) = 0 and f(1) = 1. For this example I will take f(x) = x^2.

To get a random numbers from here - with more values concentrated closer to 0 - we can do the following:

numbers = ceil(max * f(rand()))

where ceil is the ceiling function, max is the highest output you would like, f() is the function you chose, and rand() gives a random number between zero and one. Do note that the outputs of this function would be in a range from 1 to max and not 0 to max.

The following graph should give you some idea of why this actually works:

Graph of <code>ceil(f(x))</code> for <code>max == 10</code>)

Notice there is a diminishing chance of an integer being chosen as the integers grow larger - i.e. the ceil(max*f(x)) is equal to one the "longest" and 10 the "shortest".

If you would like a direct relationship between the number chosen and its magnitude you would simply need to chose a different f(x). However, at this point this is turning into more of a mathematics question than anything else. I will look for a proper f(x) - if i understand what you are looking for at least and get back to you. I am guessing as of now f(x) will be e^x but I will double check.

I hope this helps!


A quick code example:

public int weightedRandom(int max, Random rand) {
     return Math.ceil(((double) max) * Math.pow(rand.nextDouble(), 2));
}

I also printed a couple out in a java program and got the following list where max == 10:

2.0, 6.0, 8.0, 3.0, 2.0, 2.0, 1.0, 1.0, 1.0, 1.0, 7.0, 1.0, 4.0, 1.0, 1.0, 6.0, 8.0, 9.0, 7.0, 5.0
like image 103
k.krol.27 Avatar answered Sep 24 '22 17:09

k.krol.27


I would propose to use : public double nextGaussian() method from java.util.Random This allow to have a distribution with more elements around the average

I won't explain again what it is written there Javamex nextGaussian (if you want more details)

So in fact you want values between 0 and n :

the method will give values like this :

  • 70% at 1 deviation from average
  • 95% at 2 deviation from average
  • 99% at 3 deviation from average

with deviation of 1 with nothing


 Random r = new Random();
 int n = 10;
 int res = (int) Math.min(n, Math.abs(r.nextGaussian()) * n / 3);

So :

  • multiply by n : deviation becomes n
  • divide by 4 : use the fact that you can values further than the deviation (99% at 3 deviation), with that about 99% values will be under the deviation (your n)
  • use Math.abs because it's symetric with 0 for middle
  • use Math.min as a final check in case a value is higher than n

Test on 10 000 iterations :

Histogram

like image 22
azro Avatar answered Sep 22 '22 17:09

azro