5

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.

Peter O.
  • 32,158
  • 14
  • 82
  • 96
Jacques Marais
  • 2,666
  • 14
  • 33
  • So what would you expect if the given number is 10 for example? How did you choose the 0.5, 0.8 and 0.95 numbers? – assylias Jul 13 '17 at 14:29
  • Those numbers were chosen randomly by me. I would probably expect it to be evenly spread, if that is possible in some way. @assylias – Jacques Marais Jul 13 '17 at 14:30
  • 2
    Possible duplicate of [Weighted randomness in Java](https://stackoverflow.com/questions/6737283/weighted-randomness-in-java) – EJoshuaS - Stand with Ukraine Jul 13 '17 at 14:30
  • How do you want the probability distribution to look? – k.krol.27 Jul 13 '17 at 14:31
  • @EJoshuaS That question already provides the weights. I want to calculate the weights myself. – Jacques Marais Jul 13 '17 at 14:34
  • Evenly spreading numbers is one of the easier things to do. Just create an instance of `Random` and call `nextInt(n + 1)` if `n` is the highest number you want to get. If you don't want a random integer from 0 to `n` but some other objects create an array of those and use the random integer as an index into it (`n + 1` would be the array's length in that case). – Thomas Jul 13 '17 at 14:34
  • @k.krol.27 Evenly distributed. – Jacques Marais Jul 13 '17 at 14:35
  • 1
    I'm a little confused about what you mean by the numbers being spread evenly - wouldn't that mean that every number has an equal probability of being selected? That being the case, how would it differ from ordinary randomness? At a minimum, that would result in a uniform distribution (on average), which would seem to defeat the purpose of the weights in the first place. – EJoshuaS - Stand with Ukraine Jul 13 '17 at 14:36
  • @EJoshuaS Basically the chance should be less each time the number increases. I don't really know of a way this could be done "evenly" though. – Jacques Marais Jul 13 '17 at 14:40
  • So, based on your edit, the probability of each number would decrease by the same amount? For example, if the probability of the first number is 50%, should the probability of the second number be 25%, the probability of the third number be 12.5%, etc.? (Obviously, if you were using a scheme like that, the probability of the last number would have to be equal to the probability of the second-to-last number; for example, in the example above, if you had 4 numbers, both numbers 3 and 4 would have to have probabilities of 12.5% to make sure that the probabilities added up to 100%). – EJoshuaS - Stand with Ukraine Jul 13 '17 at 14:41
  • 1
    Just throwing out a random example, obviously. "Evenly" could either mean "same percentage" or "same absolute amount." – EJoshuaS - Stand with Ukraine Jul 13 '17 at 14:42
  • @EJoshuaS Alright, then not evenly at all. Just a decreasing amount that ends up with the sum of them all being `1`, and that none of them are the same. I can't think of the correct way to say this at the moment. – Jacques Marais Jul 13 '17 at 14:47
  • @JacquesMarais i've added an answer ;) – azro Jul 13 '17 at 15:26

4 Answers4

7

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
k.krol.27
  • 403
  • 4
  • 14
  • I kind of understand what you're saying here, and it seems like it will work, but would you mind maybe giving an example of how I would implement this in code? Thank you for the answer by the way! – Jacques Marais Jul 13 '17 at 15:14
  • @JacquesMarais, what do you mean? There is already version of code, you just need to choose `f()`. All other methods are in `java.jang.Math`. – M. Prokhorov Jul 13 '17 at 15:16
3

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

azro
  • 53,056
  • 7
  • 34
  • 70
  • This answer also works, but I've already chosen another answer. Thank you for answering though! – Jacques Marais Jul 13 '17 at 15:30
  • This is a good answer too. I would have used `nextGaussian()` but was looking for the flexibility to manipulate the shape of the distribution itself - i.e. pick an f(x). – k.krol.27 Jul 13 '17 at 15:33
0

You can apply a function to your random number in order to decrease chance for numbers close to one to appear. Then you multiply by your (unreachable) maximum number: 4 in this example

int n = 4 * (1 - Math.sqrt(Math.random()))
Jerome Demantke
  • 161
  • 1
  • 8
  • You might want to elaborate a little, e.g. how it is meant to work and why this solves the OP's problem. – Thomas Jul 13 '17 at 14:38
0

"Evenly" could mean either "the probability of each successive number decreases by a fixed amount" or "the probability of each successive number decreases by a fixed percentage." For example, if you use a fixed percentage of 50% to randomly choose between 4 numbers:

  • 50% of 100% is 50%, so the probability of the first number is 50%.
  • 50% of 50% is 25%, so the probability of the second number is 25%.'
  • 50% of 25% is 12.5%, so the probability of the third number is 12.5%.
  • You need the probabilities to add up to 100%, so the probability of the last number (#4) is equal to the probability of the second-to-last number (#3) - i.e. 12.5%.

If you want to decrease by a random (but decreasing) percent each time, you can just generate a random number for the probability that's less than the probability of the previous one - i.e. if the probability of the the first one is 0.5, the probability of the second is 0.0 < p < 0.5. You'd probably want to be a little more sophisticated than this, though, or you risk having tiny percentages for the last couple of items. For example, if you randomly select 0.1 for the second item, then the probability of the third item is a random number on the range of 0.0 < p < 0.1, which is quite small, and it only gets worse from there. You may want to make the probability of consecutive items have both a min and a max (e.g. the probability of the second item is 0.3 < p < 0.5).

Note that the fact that I used < rather than <= is very important. For example, you don't want to have 0.0 <= p <= 0.5, because that would mean that it's possible that the second item would have the same probability as the third item (which you don't want) and it's also possible that the probability of all subsequent items would equal 0.0 (i.e. there'd be a 100% probability of the first number and a 0% chance of any other number, which isn't at all what you want).

The weakness of the latter strategy is that you'd have to adjust one of the probabilities to make them add up to 1.0.