您的代码很好,但这里有另外两种方法。
使用累积分布函数(“CDF”)
CDF = [[0.05,0], [0.05+0.60,1], [0.5+0.60+0.35,2]]
#=> [[0.05,0], [0.65,1], [1.0,2]]
def get_num(arr)
n = rand
arr[CDF.find { |mx,_idx| n <= mx }.last]
end
arr = [{:num=>1, :diff=>-29}, {:num=>2, :diff=>5}, {:num=>3, :diff=>25}]
get_num(arr)
#=> {:num=>2, :diff=>5}
get_num(arr)
#=> {:num=>2, :diff=>5}
get_num(arr)
#=> {:num=>3, :diff=>25}
get_num(arr)
#=> {:num=>1, :diff=>-29}
get_num(arr)
#=> {:num=>2, :diff=>5}
假设:
n = rand
#=> 0.5385005480168696
然后
a = CDF.find { |mx,_idx| n <= mx }
#=> [0.65,1]
i = a.last
#=> 1
arr[i]
#=> {:num=>2, :diff=>5}
请注意,我遵循了以下约定:find 的第二个块变量 (_idx) 的名称使用下划线开头,以向读者表明该块变量未用于块计算。通常只使用underscore (_)。
现在考虑arr 的每个元素在进行n 抽奖时被随机抽出的次数:
def outcome_fractions(arr, n)
n.times
.with_object(Hash.new(0)) { |_,h| h[get_num(arr)] += 1 }
.transform_values { |v| v.fdiv(n) }
end
从索引数组中随机选择
outcome_fractions(arr, 1_000)
#=> {{:num=>2, :diff=>5} =>0.612,
# {:num=>3, :diff=>25} =>0.328,
# {:num=>1, :diff=>-29}=>0.06}
outcome_fractions(arr, 100_000)
#=> {{:num=>3, :diff=>25} =>0.34818,
# {:num=>1, :diff=>-29}=>0.04958,
# {:num=>2, :diff=>5} =>0.60224}
请注意,随着样本量的增加,随机抽取的每个散列的比例接近其指定的总体概率(尽管“伪随机”抽取并不是真正随机的)。
不要关心outcome_fractions 的工作原理。
这是另一种更有效的方式(因为它不使用find,它执行线性搜索)但使用更多内存。
CHOICE = [*[0]*5, *[1]*60, *[2]*35]
#=> [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
# 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
# 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
# 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
# 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
# 2, 2, 2, 2, 2]
def get_num(arr)
arr[CHOICE[rand(100)]]
end
#=> {{:num=>2, :diff=>5} =>0.60029,
# {:num=>3, :diff=>25}=>0.35022,
# {:num=>1, :diff=>-29}=>0.04949}
注意:
[*[0]*5, *[1]*60, *[2]*35]
产生与
相同的数组
[[0]*5, [1]*60, [2]*35].flatten
*[0]*5 中的第一个* 是splat operator;第二个是方法Array#*。 [0]*5 #=> [0,0,0,0,0] 首先被评估。
CHOICE 有 100 个元素。如果这三个概率分别是 0.048、0.604 和 0.348,CHOICE 将有 10**3 #=> 1_000 元素(48 零、604 一个和 348 二)。