【问题标题】:How to generate a random weighted distribution of elements如何生成元素的随机加权分布
【发布时间】:2015-05-12 23:54:33
【问题描述】:

我想返回一个数组,其中包含一组根据自定义频率随机分布的唯一元素。我的实际用例是根据这些图像的流行程度的定性加权来重复轮播图像。

例如假设我有 5 个带权重的元素:

一个,20% 乙,50% C, 80% D, 10%

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

【问题讨论】:

  • 你的百分比总和应该是 100 吗?
  • 一种方法是说,对于每 10%,我在随机生成数字的域中插入 1 个数字。例如: A --> 1 or 2 , B --> 3 4 5 6 7, D --> 8 , C --> ... 根据准确度,您可以在域中为每 1% 添加 1 个数字, 5%、10%、...
  • 旁注:如果您使用更常用的名称“随机加权分布”,您将获得所有新存在的语言的大量答案 :)
  • 根据单张图片的受欢迎程度来改变单张图片的显示长度会不会更有效?比如显示 A 2 秒,B 5 秒,C 8 秒,D 1 秒?

标签: javascript arrays algorithm


【解决方案1】:

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

这将产生一个类似的数组

[“A”、“A”、“B”、“B”、“B”、“B”、“B”、“C”、“C”、“C”、“C”、“C” ", "C", "C", "C", "D"]

所以你可以像这样随机选择

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

资源:

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

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

      跟进您的 > 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
      

      【讨论】:

        【解决方案4】:

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

        例如,如果返回的数字 > 0.2,则输出 C(即 80% 的机会)。

        【讨论】:

        • 是的。但是,你的问题有点模糊。如果所有百分比必须加起来为 100,那么这基本上就是您要寻找的。实际上,唯一的技巧就是了解 random() 方法。如果不用加起来100,逻辑就复杂一点。
        • 如果返回的数字 > 0.2,而你返回 C,那么你将如何输出 B?
        猜你喜欢
        • 2017-03-10
        • 2017-12-28
        • 1970-01-01
        • 2013-11-21
        • 2011-05-01
        • 1970-01-01
        • 2017-02-17
        • 2014-11-06
        • 2013-08-24
        相关资源
        最近更新 更多