arrays - Ruby - 根据可能性从数组中选择一个元素

我有一个包含 3 个元素的数组,我想根据可能性选择一个并将其添加到另一个数组中。

例如,num 1 有 5% 的机会被选中,num 2 有 60% 的机会被选中,num 3 有 35% 的机会被选中。

arr = [{:num=>1, :diff=>-29}, {:num=>2, :diff=>5}, {:num=>3, :diff=>25}]

我从 stackoverflow 中找到了以下方法,只是想知道这是否可行?或者还有其他方法吗?

def get_num(arr)
    case rand(100) + 1
    when 1..5
        p arr[0]
    when 6..65
        p arr[1]
    when 66..100
        p arr[2]
    end
end

get_num(arr)


谢谢!

最佳答案

您的代码没问题,但还有另外两种方法。

使用累积分布函数(“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 的第二个 block 变量 (_idx) 的名称以下划线开头,以向读者表明该 block block 计算中不使用变量。通常只使用下划线 (_)。

现在考虑如果进行了 n 抽取,arr 的每个元素将被随机抽取的次数的分数:

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.0480.6040.348CHOICE 将有 10* *3 #=> 1_000 个元素(48 个零,604 个和 348 个)。

https://stackoverflow.com/questions/69339733/

相关文章:

python - 如何旋转图像以获得非空像素?

reactjs - 如何去除 Material-UI 对话框中的阴影?

c - 打印 int_fast_32_t 等数据类型的可移植和正确方法是什么

c - 尝试将二维字符串数组传递给函数并打印它

javascript - Cypress 最佳实践 - 存储和比较两个值

objective-c - Rust Cocoa - 如何迭代 NSArray

c# - 通过列表循环 foreach,为不同的值执行代码

node.js - 类型错误 : Cannot read property 'passport' o

c# - 在 C# 中,如何使用 `TryAsync` 的实例?

loops - 如何在 Kotlin 循环内更改 for 循环的计数器变量值?