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