0

I am using random class to generate random numbers between 1 and 5 like myrandom.nextInt(6) and it is working fine however i would like to know if there is a way to give a specific number a weight to increase it is probability to appear, lets say instead of %20 probability i want number "4" to have %40 probability and other numbers 1,2,3,5 share the rest of %60 probability equally. is there a way for this?

Thanks in advance.

user2864740
  • 60,010
  • 15
  • 145
  • 220
Faruk
  • 773
  • 1
  • 6
  • 20
  • 2
    No, there isn't. You will have to do this yourself. Pick a random number between 1 and the sum of the weights of the numbers and then map the result to the number corresponding to it. – Ingo Bürk Apr 11 '15 at 22:00
  • possible duplicate of [Random weighted selection Java framework](http://stackoverflow.com/questions/6409652/random-weighted-selection-java-framework) – user2864740 Apr 11 '15 at 22:13
  • @Ingo Bürk , can you give me an example for doing this mapping? – Faruk Apr 11 '15 at 22:13
  • @Faruk I posted a quick demo as an answer. – Ingo Bürk Apr 11 '15 at 22:49

3 Answers3

2

I use an array. E.g.

int[] arr = {4, 4, 4, 5, 5, 6};
int a = arr[random.nextInt(arr.length)];

For a more dynamic solution, try this. The weights do not have to add up to any particular value.

public static <T> T choice(Map<? extends T, Double> map) {
    if (map == null || map.size() == 0)
        throw new IllegalArgumentException();
    double sum = 0;
    for (double w : map.values()) {
        if (Double.compare(w, 0) <= 0 || Double.isInfinite(w) || Double.isNaN(w))
            throw new IllegalArgumentException();
        sum += w;
    }
    double rand = sum * Math.random();
    sum = 0;
    T t = null;
    for (Map.Entry<? extends T, Double> entry : map.entrySet()) {
        t = entry.getKey();
        if ((sum += entry.getValue()) >= rand)
            return t;
    }
    return t;
}

You can easily add / remove / change entries from the map whenever you like. Here is an example of how you use this method.

Map<Integer, Double> map = new HashMap<>();
map.put(1, 40.0);
map.put(2, 50.0);
map.put(3, 10.0);
for (int i = 0; i < 10; i++)
    System.out.println(choice(map));
Paul Boddington
  • 37,127
  • 10
  • 65
  • 116
  • i will increase,decrease probabilities continuously and even remove some numbers so using an array which is static is prolematic, even i use something like stringbuffer, it still is as i will have to make some calculations before adding elements to stringbuffer (to know how much i will add) – Faruk Apr 11 '15 at 22:12
  • @Faruk Ok, I'll have a think about a more dynamic solution. – Paul Boddington Apr 11 '15 at 22:14
1

You'll have to generate a larger range of numbers (like from 1 to 100) and use ranges to return the numbers you really want. Eg: (in pseudocode)

r = randint(1..100)
if (r >= 1 && r <= 20) // 20% chance
    return 1
else if (r >= 21 && r <= 60) // 40% chance
    return 2

Etc.

Matt Gregory
  • 8,074
  • 8
  • 33
  • 40
1

pbadcefp's answer is probably the easiest. Since you stated in the comments that you need it to be "dynamic", here's an alternate approach. Note that the weights basically specify how often the number appears in the array to pick from

public int weightedRandom( Random random, int max, Map<Integer, Integer> weights ) {
    int totalWeight = max;
    for( int weight : weights.values() ) {
        totalWeight += weight - 1;
    }

    int choice = random.nextInt( totalWeight );
    int current = 0;
    for( int i = 0; i < max; i++ ) {
        current += weights.containsKey( i ) ? weights.get( i ) : 1;
        if( choice < current ) {
            return i;
        }
    }

    throw new IllegalStateException();
}

Example usage:

Map<Integer, Integer> weights = new HashMap<>();
weights.put( 1, 0 ); // make choosing '1' impossible
weights.put( 4, 3 ); // '4' appears 3 times rather than once

int result = weightedRandom( new Random(), 5, weights );

Basically, this is equivalent to pbadcefp's solution applied on the array { 0, 2, 3, 4, 4, 4 }

You will have to adapt this if you want to use percentages. Just calculate weights accordingly. Also, I didn't test cornercases on this, so you might want to test this a little bit more extensively.

This is by no means a complete solution, but I prefer giving something to work on over complete solutions since you should do some of the work yourself.

I'll also go on record and say that IMHO this is over-engineered; but you wanted something like this.

Ingo Bürk
  • 19,263
  • 6
  • 66
  • 100