Я предполагаю, что вы будете рисовать несколько случайных значений, в этом случае важна эффективность.Более того, я предполагаю, что все имена уникальны и все проценты положительны (то есть, что пары с процентами 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]
Идея состоит в том, что мы сгенерируем (псевдо) случайное число от нуля до единицы, r
инайдите первое значение c
CDF, для которого r <= c
. 1 . Чтобы сделать это эффективным способом, мы выполним интеллектуальный поиск CDF.Это возможно, потому что CDF является возрастающей функцией.
Я сделаю бинарный поиск, используя Array # bsearch_index .Этот метод по сути такой же, как Array # bseach (чей документ является релевантным), за исключением того, что возвращается индекс 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.