按百分比选择数组中的项目

时间:2018-05-19 15:15:00

标签: ruby

我有一个包含名称和百分比的数组。 例: [["JAMES", 3.318], ["JOHN", 3.271], ["ROBERT", 3.143]]。 现在我有大约一千个这些名字,我正在试图弄清楚如何根据名称的百分比随机选择一个名字(比如James如何为3.318%,John为3.271%),所以这个名字会有被选中的百分比(罗伯特将有3.143%被选中)。帮助将不胜感激。

4 个答案:

答案 0 :(得分:5)

您可以使用max_by :(文档包含类似示例)

array.max_by { |_, weight| rand ** 1.fdiv(weight) }

这假设您的权重是实际百分比,即3.1%必须表示为0.031。或者,如果您不想调整权重:

array.max_by { |_, weight| rand ** 100.fdiv(weight) }

我在这里使用fdiv来说明可能的整数值。如果你的权重总是浮动,你也可以使用/

答案 1 :(得分:3)

尽管我喜欢@Stefan回答的不仅仅是我的回答,但我会提供一个可能的解决方案:我会沿100.0分配所有百分比,以便它们从0.0开始并结束到100.0。 想象一下,我有一个具有以下百分比的数组:

a = [10.5, 20.5, 17.8, 51.2]

其中

a.sum = 100.0

我们可以编写以下内容以沿100.0分发它们:

sum = 0.0
b = a.map { |el| sum += el }

,结果将是

b = [10.5, 31.0, 48.8, 100.0]

现在我可以生成0.0到100.0之间的随机数:

r = rand(0.0..100.0) # or r = rand * 100.0

想象r是45.32

我选择b的第一个元素是> = r`

idx = b.index { |el| el >= r }

在我们的案例中会返回2

现在您可以选择a[idx]

但我也会和@Stefan一起回答:)

答案 2 :(得分:2)

我假设您将绘制多个随机值,在这种情况下效率很重要。此外,我假设所有名称都是唯一的,并且所有百分比都是正数(即已删除0.0百分比的对。)

您将获得相当于(离散)概率密度函数(PDF)的数量。第一步是将其转换为累积密度函数(CDF)。

假设我们得到以下数组(其百分比总和为100)。

arr = [["LOIS", 28.16], ["JAMES", 22.11], ["JOHN", 32.71], ["ROBERT", 17.02]]

首先,将名称与百分比分开。

names, probs = arr.transpose
  #=> [["LOIS", "JAMES", "JOHN", "ROBERT"],
  #     [28.16, 22.11, 32.71, 17.02]]

接下来计算CDF。

cdf = probs.drop(1).
            each_with_object([0.01 * probs.first]) { |pdf, cdf|
              cdf << 0.01 * pdf + cdf.last }
  #=> [0.2816, 0.5027, 0.8298, 1.0]

我们的想法是,我们将在0到1 r之间生成一个(伪)随机数,并找到c的CDF的第一个值r <= c 1 为了有效地完成这项工作,我们将对CDF进行智能搜索。这是可能的,因为CDF是一种增加的功能。

我将使用Array#bsearch_index进行二分查找。此方法与Array#bseach(其doc是相关的)基本相同,除了返回cdf的索引而不是随机选择cdf的元素。很快就会明白我们为什么要索引。

r = rand
  #=> 0.6257547400776025
idx = cdf.bsearch_index { |c| r <= c }
  #=> 2

请注意,我们无法编写cdf.bsearch_index { |c| rand <= c },因为每次评估块时都会执行rand

因此,随机选择的名称是 2

names[idx]
  #=> "JOHN"

现在让我们把所有这些放在一起。

def setup(arr)
  @names, probs = arr.transpose
  @cdf = probs.drop(1).
    each_with_object([0.01*probs.first]) { |pdf, cdf| cdf << 0.01 * pdf + cdf.last }
end

def random_name
  r = rand
  @names[@cdf.bsearch_index { |c| r <= c }]
end

让我们试一试。执行setup以计算实例变量@names@cdf

setup(arr)
@names
  #=> ["LOIS", "JAMES", "JOHN", "ROBERT"]
@cdf
  #=> [0.2816, 0.5027, 0.8298, 1.0]

然后每次需要随机名称时调用random_name

5.times.map { random_name }
  #=> ["JOHN", "LOIS", "JAMES", "LOIS", "JAMES"]

1。这就是在仿真模型中生成大多数离散随机变量的方法。

2。如果我使用bsearch而不是bsearch_index,我将不得不提前创建一个带有cdf=>name键值对的哈希,以便为给定的随机选择的CDF值检索名称。 / SUP>

答案 3 :(得分:1)

这是我解决问题的方法:

array = [["name1", 33],["name2", 20],["name3",10],["name4",7],["name5", 30]]

def random_name(array)
  random_number = rand(0.000..100.000) 
  sum = 0

array.each do |x|
  if random_number.between?(sum, sum + x[1])
    return x[0]
  else
    sum += x[1]
  end
end
end

puts random_name(array)