选择随机选项,其中每个选项具有不同的被挑选概率

时间:2013-10-09 00:43:28

标签: ruby algorithm random

假设您有三个"选项",ABC

您的算法必须选择并返回一个随机的算法。为此,将它们放入数组{A,B,C}并生成一个随机数(0,1或2)非常简单,该数字将是要返回的数组中元素的索引。

现在,此算法有一个变体:假设A被选中的几率为40%,B为20%,C为40%。如果是这种情况,您可以采用类似的方法:生成数组{A,A,B,C,C}并使用随机数(0,1,2,3,4)来选择要返回的元素。

有效。但是,我觉得这是非常低效的。想象一下,将此算法用于大量数量的选项。您将创建一个有点大的数组,可能有100个元素,每个元素代表1%。现在,这仍然不是很大,但假设你的算法每秒使用很多次,这可能很麻烦。


我考虑过制作一个名为Slot的课程,该课程有两个属性:.value.size。为每个选项创建一个插槽,其中.value属性是选项的值,.size一个等于数组中此类选项的出现次数。然后生成一个从0到总发生次数的随机数,并检查该数字落在哪个插槽上。

我更关心这个算法,但这是我对Ruby的尝试:

class Slot
  attr_accessor :value
  attr_accessor :size
  def initialize(value,size)
    @value = value
    @size  = size
  end
end

def picker(options)
  slots = []
  totalSize = 0
  options.each do |value,size|
    slots << Slot.new(value,size)
    totalSize += size
  end
  pick = rand(totalSize)
  currentStack = 0
  slots.each do |slot|
    if (pick <= currentStack + slot.size)
      return slot.value
    else
      currentStack += slot.size
    end
  end
  return nil
end

50.times do
  print picker({"A" => 40, "B" => 20, "C" => 40})
end

哪个输出:

  

CCCCACCCCAAACABAAACACACCCAABACABABACBAAACACCBACAAB


是否有更有效的方法来实现选择随机选项的算法,其中每个选项都有不同的被选择概率?

3 个答案:

答案 0 :(得分:12)

最简单的方法可能是写一个案例陈述:

def get_random()
  case rand(100) + 1
    when  1..50   then 'A'
    when 50..75   then 'B'
    when 75..100  then 'C'
  end
end

问题在于你无法传递任何选项,因此如果你想让它能够选择,你可以编写这样的函数。下面的内容非常类似于您编写的内容,但有点短:

def picker(options)
  current, max = 0, options.values.inject(:+)
  random_value = rand(max) + 1
  options.each do |key,val|
     current += val
     return key if random_value <= current
  end
end

# A with 25% prob, B with 75%.
50.times do
  print picker({"A" => 1, "B" => 3})
end
# => BBBBBBBBBABBABABBBBBBBBABBBBABBBBBABBBBBBABBBBBBBA

# If you add upp to 100, the number represent percentage.
50.times do
  print picker({"A" => 40, "T" => 30, "C" => 20, "G" => 10})
end
# => GAAAATATTGTACCTCAATCCAGATACCTTAAGACCATTAAATCTTTACT 

答案 1 :(得分:6)

虽然这不是一个直接的答案,但我会向您展示一个帮助您概述此问题的来源:http://www.av8n.com/physics/arbitrary-probability.htm

修改

刚刚在红宝石中找到了一个很好的来源,pickup gem

require 'pickup'
headings = {
  A: 40,
  B: 20,
  C: 40,
}
pickup = Pickup.new(headings)
pickup.pick
#=> A
pickup.pick
#=> B
pickup.pick
#=> A
pickup.pick
#=> C
pickup.pick
#=> C

答案 2 :(得分:6)

作为更高效算法的第一个近似值,如果计算累积分布函数(这只是分布函数的一次传递,计算运行总和),那么您可以使用a找到随机选择的整数的位置二进制搜索而不是线性搜索。如果您有很多选项,这将有所帮助,因为它将搜索时间从O(#options)减少到O(log #options)。

但是有一个O(1)解决方案。这是基本的大纲。

假设我们有N个选项,1...N,权重为ω1...ωN,其中所有ω值至少为0.为简单起见,我们缩放权重,使其均值为{{1}或者换句话说,他们的总和是1。 (我们只是将它们乘以N。我们实际上不必这样做,但是如果没有MathJax,它会使下一段更容易输入。)

现在,创建一个N/Σω元素的向量,其中每个元素都有两个选项标识符(Nlo)和一个截止hi。选项标识符只是整数p,而1...N将计算为p范围内的实数。

我们继续按如下方式填写向量。对于每个元素(0, 1.0)依次:

  • 如果某些i正好是ωj,那么我们设置:
    1.0
       loi = j
       hii = j
    我们会从权重列表中删除   pi = 1.0

  • 否则,必须有一些ωj和一些ωj < 1.0。 (那是因为平均重量是1.0,并且没有一个具有平均值。其中一些必须少一些,一些更多,因为所有元素都不可能大于平均值或所有元素都少于比平均值。)现在,我们设置:
    ωk > 1.0
       loi = j
       hii = k
       pi = ωj
    我们再次从权重中移除   ωk = ωk - (1 - ωj)

请注意,在这两种情况下,我们都删除了一个重量,并且我们将权重之和减少了1.0。所以平均重量仍然是1.0。

我们继续这种方式,直到填满整个矢量。 (最后一个元素将有ωj)。

鉴于此向量,我们可以选择加权随机选项,如下所示:

  • p = 1.0范围内生成随机整数i,在1...N范围内生成随机浮点值r。如果(0, 1.0],我们会选择选项r < pi;否则,我们选择选项loi

应该清楚为什么这可以从矢量的构造起作用。每个高于平均权重的选项的权重分布在各种向量元素中,而每个低于平均权重的选项被分配给某个向量元素的一部分,并具有相应的选择概率。

在实际实现中,我们将权重范围映射到整数值,并使总权重接近最大整数(它必须是hii的倍数,因此会有一些晃动。然后我们可以选择一个槽并从一个随机整数中选择槽内的权重。实际上,我们可以修改算法以通过添加一些0加权选项来强制插槽的数量为2的幂来避免除法。因为整数运算不能很好地完成,所以需要稍微摆弄一下,但最终结果可以统计正确,模数正在使用的PRNG的特性,并且它将执行几乎N选项的简单未加权选择一样快(一个班次和几个比较额外),代价是占用少于N个存储元素的向量(计算必须的可能性)几乎是插槽数量的两倍。