如何生成元素的随机加权分布

时间:2015-05-12 23:54:33

标签: javascript arrays algorithm

我想返回一个数组,该数组具有一组根据自定义频率随机分布的唯一元素。我的真实世界用例是基于对图像流行程度的定性加权重复轮播图像。

E.g。假设我有5个带权重的元素:

A,20% B,50% C,80% D,10%

我想写一个函数,给定一个长度,试图近似一个序列,使得C出现的频率是D的八倍; D的出现次数比B少5次; A的出现次数比C少三倍。

4 个答案:

答案 0 :(得分:7)

  

C的出现次数是D的8倍; D的出现次数比B少5次; A的出现次数比C少三倍。

您可以使用元素的加权数组执行此操作:

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);

这会生成一个像

这样的数组
  

[&#34; A&#34;,&#34; A&#34;,&#34; B&#34;,&#34; B&#34;,&#34; B&#34;,& #34; B&#34;,&#34; B&#34;,&#34; C&#34;,&#34; C&#34;,&#34; C&#34;,&#34; C&# 34;,&#34; C&#34;,&#34; C&#34;,&#34; C&#34;,&#34; C&#34;,&#34; D&#34;]

所以你可以随意选择

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

资源:

答案 1 :(得分:3)

假设您将分配编号作为对象数组,如下所示:

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

这消除了您的权重加起来为100%的任何假设 - 它们可能是点击次数,投票或您喜欢的任何其他值。然后你可以这样做:

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;
    }
}

我不确定这与其他效率实现相比如何,但它很简洁。

答案 2 :(得分:2)

扩展a_gupta的答案:

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])

跟进你的&gt; 100%示例,您可以重新计算权重,使它们等于1(对于有效概率)

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

答案 3 :(得分:1)

有一个非常简单的解决方案。 random()方法返回0到1之间的数字。

例如,如果返回的数字是&gt; 0.2,然后输出C(即80%几率)。