How to generate a random weighted distribution of elements

前端 未结 4 345
执笔经年
执笔经年 2021-01-13 07:04

I would like to return an array which has a set of unique elements randomly distributed according to custom frequency. My real world use-case is the repetition of carousel i

相关标签:
4条回答
  • 2021-01-13 07:09

    C will appear eight times more often than D; D will appear 5 times less often than B; A will appear three times less often than C.

    You can do that with a weighted array of your elements:

    var elems = ["A", "B", "C", "D"];
    var weights = [2, 5, 8, 1]; // weight of each element above
    var totalWeight = weights.reduce(add, 0); // get total weight (in this case, 16)
    
    function add(a, b) { return a + b; } // helper function
    
    var weighedElems = [];
    var currentElem = 0;
    while (currentElem < elems.length) {
      for (i = 0; i < weights[currentElem]; i++)
        weighedElems[weighedElems.length] = elems[currentElem];
      currentElem++;
    }
    
    console.log(weighedElems);
    

    This will produce an array like

    ["A", "A", "B", "B", "B", "B", "B", "C", "C", "C", "C", "C", "C", "C", "C", "D"]

    so you can choose randomly from that like so

    var rnd = Math.floor(Math.random() * totalWeight);
    console.log(weighedElems[rnd]);
    

    Resources:

    • Generate A Weighted Random Number
    • Weighted random number generation in Javascript
    • Algorithm for generating weighed random numbers
    0 讨论(0)
  • 2021-01-13 07:13

    To expand on a_gupta's answer:

    function pick_bin(binProbabilities){     // e.g. [0.1, 0.3, 0.3, 0.3]
      var cumulative = [];                   // e.g. [0.1, 0.4, 0.7, 1]
      var accumulator = 0;
    
      // Iterating over an array with forEach:
      binProbabilities.forEach(function(item, index){
        var prob = Number(item);
        accumulator += prob;
        cumulative[index] = accumulator;
      })
    
      if(accumulator !== 1){
        throw new Error('Sum of binProbabilities must equal 1')
      }
    
      var n = binProbabilities.length;
      var rFloat = Math.random();
    
      // Iterating over an array with for:
      for(var i=0; i<n; i++){
        var pcI = cumulative[i];      // cumulative probability of this index
        if(pcI >= rFloat){            // Found the first bin fitting the random number
          console.log(i);
          return i;
        }
      }
    }
    
    pick_bin([1]); // returns 0 every time
    pick_bin([.5, .5]) // returns 0,1 50/50
    pick_bin([0.1, 0.3, 0.3, 0.3])
    

    followup for your > 100% example, you can recalculate the weights to make them equal 1 (for a valid probability)

    Desired weightings:     20% 50% 80% 10%
    Sum these weights:      20 + 50 + 80 + 10 = 160
    Divide each by the sum: 2/16, 5/16, 8/16, 1/16
    Now they sum to 1
    
    0 讨论(0)
  • 2021-01-13 07:20

    There is a very simple solution. The random() method returns a number between 0 and 1 inclusive.

    Eg if the number returned is > 0.2, then output C (ie 80% chance).

    0 讨论(0)
  • 2021-01-13 07:33

    Let's say you take your distribution numbers as an array of objects, like this:

    var items = [
        {item: "A", weight: 20}, 
        {item: "B", weight: 50}, 
        {item: "C", weight: 80},
        {item: "D", weight: 10}
    ];
    

    This removes any assumption that your weights add up to 100% - they might be click-counts, or votes, or any other value you like. Then you can do this:

    function weightedSelect(items) {
        // Get the total, and make the weights cummulative
        var total = items.reduce(function(sum, item){
            item.weight = item.weight + sum;
            return item.weight;
        },0);
    
        var r = Math.random() * total;
    
        // Can't use .forEach() here because we want early termination
        for (var i = 0; i < items.length; i++) {
             if (r < items[i].weight)
                 return items[i].item;
        }
    }
    

    I'm not sure how this compares to the other implementations for efficiency, but it's concise.

    0 讨论(0)
提交回复
热议问题