【问题标题】:In Ruby, how can one make a weighted random selection by least weight?在 Ruby 中,如何通过最小权重进行加权随机选择?
【发布时间】:2019-05-09 15:16:44
【问题描述】:

如果我有数组:

ar = [1,3,5,3,6,1,4,6,7,6,6,6,6,6]

我可以将其减少到出现次数:

counts = {1=>2, 3=>2, 5=>1, 6=>7, 4=>1, 7=>1}

现在我想随机选择 ar 中使用最少的数字被更多加权

我了解如何根据最常用的数字轻松做出加权随机选择,但不是它的倒数。

【问题讨论】:

  • 您的数组有14 元素,其中两个是1。那么选择1 的概率应该是多少呢?您想如何应用权重?
  • 简单的答案是简单地反转权重并使用现有机制。还有更复杂(和直接)的机制,但从长远来看,它们都可能相似。
  • 如何恢复权重顺序(使它们为负数或将 1 除以权重等 - 任何恢复顺序的转换都应该有效)并解决您已经了解解决方案的任务?
  • 这一切都取决于预期的权重。选择5 的可能性是否应该是1两倍
  • 选择任何单调递减函数,在给定频率计算其值,考虑其相对概率,归一化,采样。

标签: ruby random weighted


【解决方案1】:

这似乎对你有用:

arr = [1,3,5,3,6,1,4,6,7,6,6,6,6,6]

arr.group_by(&:itself).transform_values{|v| arr.size / v.size}.flat_map do |k,v| 
 [k] * v
end.sample

我们对元素进行分组并对它们进行计数,然后我们创建一个新的Array,并将元素的数量倒置以有利于出现较少的情况。例如

arr.group_by(&:itself).transform_values{|v| arr.size / v.size}.flat_map do |k,v| 
 [k] * v
end.group_by(&:itself).transform_values(&:size)
#=> {1=>7, 3=>7, 5=>14, 6=>2, 4=>14, 7=>14}

由于 5 最初出现一次,它现在出现了 14 次(与 4 和 7 相同)。 所以 5,4 和 7 被选中的可能性是相同的,它们的可能性是 1 和 3 的两倍,而 1 和 3 的可能性是 6 的两倍和 7 倍。

也许这样的事情可能更有效

grouping =arr.group_by(&:itself).transform_values(&:size).
scale = grouping.values.uniq.reduce(&:lcm)

grouping.flat_map do |k, v|
  [k]  * (scale / v)
end.sample

【讨论】:

  • 干得好。这是一个出色的 Ruby 响应。我以前从未听说过&:itself
  • @Trip 请注意:建议的解决方案简洁而优雅,但如果输入数组仅包含唯一值,它会在尝试从中采样随机值之前显着增加(平方)数组大小。尝试创建一个像arr = (0..999).map { rand(100000) } 这样的输入数组,然后将上面的代码应用到它上面——您将获得一个包含大约 1M 条记录的数组来进行采样。对于只有 50K 唯一项的数组,使用此方法获取加权随机值在我的笔记本电脑上需要将近 10 秒...
  • @KonstantinStrukov 我同意我们肯定可以采用更高效的解决方案
  • @KonstantinStrukov 我们可以确定组大小的 lcm 以提高效率,因为您的代码会立即返回。
  • 你可以通过调用单调递减函数来进一步抽象它。您使用反函数,但实际上任何人都可以完成这项工作。比如说,1/exp(frequency) 将使选择概率非常尖锐,而 1/log(1+frequency) 将是宽松的...计算所有频率的此函数,使其成为相对概率,标准化,采样。
【解决方案2】:

如果您已经有一种用于进行随机加权选择的算法,那么交换权重的一个选项可以如下。

grouping = ar.group_by { |n| n }.transform_values(&:size)
#=> {1=>2, 3=>2, 5=>1, 6=>7, 4=>1, 7=>1}
weights = grouping.values.uniq.sort
#=> [1, 2, 7]
reverse_mapping = weights.zip(weights.reverse).to_h
#=> {1=>7, 2=>2, 7=>1}
grouping.transform_values{ |v| reverse_mapping[v] }
#=> {1=>2, 3=>2, 5=>7, 6=>1, 4=>7, 7=>7}

就是这样。


可以重构为更 Rubyish:
res = ar.group_by { |n| n }.transform_values(&:size).then do |h|
  rev_map = h.values.uniq.sort.then { |w| w.zip(w.reverse).to_h }
  h.transform_values{ |v| rev_map[v] }
end

#=> {1=>2, 3=>2, 5=>7, 6=>1, 4=>7, 7=>7}

【讨论】:

  • 你可以使用 &:itself
猜你喜欢
  • 2011-04-29
  • 2015-07-05
  • 2010-09-08
  • 2018-12-14
  • 2017-12-26
  • 1970-01-01
  • 2023-03-31
相关资源
最近更新 更多