Generate A Weighted Random Number

前端 未结 11 2204
予麋鹿
予麋鹿 2020-11-22 12:40

I\'m trying to devise a (good) way to choose a random number from a range of possible numbers where each number in the range is given a weight. To put it simply: given the

相关标签:
11条回答
  • 2020-11-22 12:56

    I suggest to use a continuous check of the probability and the rest of the random number.

    This function sets first the return value to the last possible index and iterates until the rest of the random value is smaller than the actual probability.

    The probabilities have to sum to one.

    function getRandomIndexByProbability(probabilities) {
        var r = Math.random(),
            index = probabilities.length - 1;
    
        probabilities.some(function (probability, i) {
            if (r < probability) {
                index = i;
                return true;
            }
            r -= probability;
        });
        return index;
    }
    
    var i,
        probabilities = [0.8, 0.1, 0.1],
        count = probabilities.map(function () { return 0; });
    
    for (i = 0; i < 1e6; i++) {
        count[getRandomIndexByProbability(probabilities)]++;
    }
    
    console.log(count);
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    0 讨论(0)
  • 2020-11-22 13:03

    Here are 3 solutions in javascript since I'm not sure which language you want it in. Depending on your needs one of the first two might work, but the the third one is probably the easiest to implement with large sets of numbers.

    function randomSimple(){
      return [0,0,0,0,0,0,0,0,1,2][Math.floor(Math.random()*10)];
    }
    
    function randomCase(){
      var n=Math.floor(Math.random()*100)
      switch(n){
        case n<80:
          return 0;
        case n<90:
          return 1;
        case n<100:
          return 2;
      }
    }
    
    function randomLoop(weight,num){
      var n=Math.floor(Math.random()*100),amt=0;
      for(var i=0;i<weight.length;i++){
        //amt+=weight[i]; *alternative method
        //if(n<amt){
        if(n<weight[i]){
          return num[i];
        }
      }
    }
    
    weight=[80,90,100];
    //weight=[80,10,10]; *alternative method
    num=[0,1,2]
    
    0 讨论(0)
  • 2020-11-22 13:03

    8 years late but here's my solution in 3 lines.

    1) Prepare an array of probability mass function such that

    pmf[array_index] = P(X=array_index):

    var pmf = [0.8, 0.1, 0.1]
    

    2) Prepare an array for the corresponding cumulative distribution function such that

    cdf[array_index] = F(X=array_index):

    var cdf = pmf.map((sum => value => sum += value)(0))
    // [0.8, 0.9, 1]
    

    3a) Generate a random number.

    3b) Get an array of elements which are more than or equal to this number.

    3c) Return its length.

    cdf.filter(el => Math.random() >= el).length
    
    0 讨论(0)
  • 2020-11-22 13:09

    This is more or less a generic-ized version of what @trinithis wrote, in Java: I did it with ints rather than floats to avoid messy rounding errors.

    static class Weighting {
    
        int value;
        int weighting;
    
        public Weighting(int v, int w) {
            this.value = v;
            this.weighting = w;
        }
    
    }
    
    public static int weightedRandom(List<Weighting> weightingOptions) {
    
        //determine sum of all weightings
        int total = 0;
        for (Weighting w : weightingOptions) {
            total += w.weighting;
        }
    
        //select a random value between 0 and our total
        int random = new Random().nextInt(total);
    
        //loop thru our weightings until we arrive at the correct one
        int current = 0;
        for (Weighting w : weightingOptions) {
            current += w.weighting;
            if (random < current)
                return w.value;
        }
    
        //shouldn't happen.
        return -1;
    }
    
    public static void main(String[] args) {
    
        List<Weighting> weightings = new ArrayList<Weighting>();
        weightings.add(new Weighting(0, 8));
        weightings.add(new Weighting(1, 1));
        weightings.add(new Weighting(2, 1));
    
        for (int i = 0; i < 100; i++) {
            System.out.println(weightedRandom(weightings));
        }
    }
    
    0 讨论(0)
  • 2020-11-22 13:10

    here is the input and ratios : 0 (80%), 1(10%) , 2 (10%)

    lets draw them out so its easy to visualize.

                    0                       1        2
    -------------------------------------________+++++++++
    

    lets add up the total weight and call it TR for total ratio. so in this case 100. lets randomly get a number from (0-TR) or (0 to 100 in this case) . 100 being your weights total. Call it RN for random number.

    so now we have TR as the total weight and RN as the random number between 0 and TR.

    so lets imagine we picked a random # from 0 to 100. Say 21. so thats actually 21%.

    WE MUST CONVERT/MATCH THIS TO OUR INPUT NUMBERS BUT HOW ?

    lets loop over each weight (80, 10, 10) and keep the sum of the weights we already visit. the moment the sum of the weights we are looping over is greater then the random number RN (21 in this case), we stop the loop & return that element position.

    double sum = 0;
    int position = -1;
    for(double weight : weight){
    position ++;
    sum = sum + weight;
    if(sum > 21) //(80 > 21) so break on first pass
    break;
    }
    //position will be 0 so we return array[0]--> 0
    

    lets say the random number (between 0 and 100) is 83. Lets do it again:

    double sum = 0;
    int position = -1;
    for(double weight : weight){
    position ++;
    sum = sum + weight;
    if(sum > 83) //(90 > 83) so break
    break;
    }
    
    //we did two passes in the loop so position is 1 so we return array[1]---> 1
    
    0 讨论(0)
提交回复
热议问题