Генерировать случайные числа с заданным распределением
проверьте этот вопрос:
быстрая вероятность выбора случайного числа?
верхний ответ предлагает использовать оператор switch, который выполняет эту работу. Однако, если у меня есть очень большое количество случаев для рассмотрения, код выглядит очень неэлегантным; у меня есть гигантский оператор switch с очень похожим кодом в каждом случае, повторяемом снова и снова.
есть ли более приятный, более чистый способ выбрать случайное число с определенной вероятностью когда у вас есть большое количество вероятностей для рассмотрения? (как ~30)
4 ответов
это быстрая реализация, на которую сильно влияют различные ответы генерировать случайные числа с заданным (числовым) распределением:
func randomNumber(#probabilities: [Double]) -> Int {
// Sum of all probabilities (so that we don't have to require that the sum is 1.0):
let sum = reduce(probabilities, 0, +)
// Random number in the range 0.0 <= rnd < sum :
let rnd = sum * Double(arc4random_uniform(UInt32.max)) / Double(UInt32.max)
// Find the first interval of accumulated probabilities into which `rnd` falls:
var accum = 0.0
for (i, p) in enumerate(probabilities) {
accum += p
if rnd < accum {
return i
}
}
// This point might be reached due to floating point inaccuracies:
return (probabilities.count - 1)
}
примеры:
let x = randomNumber(probabilities: [0.2, 0.3, 0.5])
возвращает 0 с вероятностью 0.2, 1 с вероятностью 0.3, и 2 с вероятностью 0,5.
let x = randomNumber(probabilities: [1.0, 2.0])
возврат 0 с вероятностью 1/3 и 1 с вероятностью 2/3.
обновление Swift 2 / Xcode 7:
func randomNumber(probabilities probabilities: [Double]) -> Int {
// Sum of all probabilities (so that we don't have to require that the sum is 1.0):
let sum = probabilities.reduce(0, combine: +)
// Random number in the range 0.0 <= rnd < sum :
let rnd = sum * Double(arc4random_uniform(UInt32.max)) / Double(UInt32.max)
// Find the first interval of accumulated probabilities into which `rnd` falls:
var accum = 0.0
for (i, p) in probabilities.enumerate() {
accum += p
if rnd < accum {
return i
}
}
// This point might be reached due to floating point inaccuracies:
return (probabilities.count - 1)
}
обновление Swift 3 / Xcode 8:
func randomNumber(probabilities: [Double]) -> Int {
// Sum of all probabilities (so that we don't have to require that the sum is 1.0):
let sum = probabilities.reduce(0, +)
// Random number in the range 0.0 <= rnd < sum :
let rnd = sum * Double(arc4random_uniform(UInt32.max)) / Double(UInt32.max)
// Find the first interval of accumulated probabilities into which `rnd` falls:
var accum = 0.0
for (i, p) in probabilities.enumerated() {
accum += p
if rnd < accum {
return i
}
}
// This point might be reached due to floating point inaccuracies:
return (probabilities.count - 1)
}
обновление Swift 4.2 / Xcode 10:
func randomNumber(probabilities: [Double]) -> Int {
// Sum of all probabilities (so that we don't have to require that the sum is 1.0):
let sum = probabilities.reduce(0, +)
// Random number in the range 0.0 <= rnd < sum :
let rnd = Double.random(in: 0.0 ..< sum)
// Find the first interval of accumulated probabilities into which `rnd` falls:
var accum = 0.0
for (i, p) in probabilities.enumerated() {
accum += p
if rnd < accum {
return i
}
}
// This point might be reached due to floating point inaccuracies:
return (probabilities.count - 1)
}
есть ли более приятный, более чистый способ выбрать случайное число с определенной вероятностью, когда у вас есть большое количество вероятностей для рассмотрения?
конечно. Напишите функцию, которая генерирует число на основе таблицы вероятностей. Это по существу то, на что вы указали в инструкции switch: таблица, определенная в коде. Вы можете сделать то же самое с данными, используя таблицу, которая определена как список вероятностей и результаты:
probability outcome ----------- ------- 0.4 1 0.2 2 0.1 3 0.15 4 0.15 5
теперь вы можете выбрать число от 0 до 1 наугад. Начиная с верхней части списка, складывайте вероятности до тех пор, пока не превысите выбранное число, и используйте соответствующий результат. Например, предположим, что выбранное вами число равно 0,6527637. Начните сверху: 0.4 меньше, поэтому продолжайте идти. 0.6 (0.4 + 0.2) меньше, так что продолжай. 0.7 (0.6 + 0.1) больше, так что хватит. Исход 3.
Я держал таблицу короткой здесь ради ясность, но вы можете сделать это так долго, как вам нравится, и вы можете определить его в файле данных, чтобы вам не пришлось перекомпилировать при изменении списка.
обратите внимание, что в Swift нет ничего особенного в этом методе-вы можете сделать то же самое в C или Swift или Lisp.
вы можете сделать это с экспоненциальными или квадратичными функциями - x-ваше случайное число, возьмите y как новое случайное число. Затем вам просто нужно покачать уравнение, пока оно не подойдет к вашему случаю использования. Скажем, у меня было (x^2)/10 + (x / 300). Поместите свое случайное число в (как некоторую форму с плавающей запятой), а затем получите пол с Int (), когда он выйдет. Таким образом, если мой генератор случайных чисел идет от 0 до 9, у меня есть 40% шанс получить 0, и 30% шанс получить 1-3, 20% шанс получить 4-6, и 10% шанс 8. Вы в основном пытаетесь подделать какое-то нормальное распределение.
вот представление о том, как это будет выглядеть в Swift:
func giveY (x: UInt32) -> Int {
let xD = Double(x)
return Int(xD * xD / 10 + xD / 300)
}
let ans = giveY (arc4random_uniform(10))
EDIT:
Я не был очень ясен выше - я имел в виду, что вы можете заменить оператор switch некоторой функцией, которая вернет набор чисел с распределением вероятности, которое вы можете выяснить с помощью регрессии с помощью вольфрама или чего-то еще. Итак, для вопроса, с которым вы связаны, вы можете сделайте что-нибудь вроде этого:
import Foundation
func returnLevelChange() -> Double {
return 0.06 * exp(0.4 * Double(arc4random_uniform(10))) - 0.1
}
newItemLevel = oldItemLevel * returnLevelChange()
таким образом, эта функция возвращает double где-то между -0.05 и 2.1. Это будет ваша цифра" x% хуже/лучше, чем текущий уровень элемента". Но, поскольку это экспоненциальная функция, она не вернет четный разброс чисел. Arc4random_uniform (10) возвращает int из 0 - 9, и каждый из них приведет к двойнику, как это:
0: -0.04
1: -0.01
2: 0.03
3: 0.1
4: 0.2
5: 0.34
6: 0.56
7: 0.89
8: 1.37
9: 2.1
С каждого из этих Интс от arc4random_uniform имеет равный шанс появится получить вероятности, как это:
40% chance of -0.04 to 0.1 (~ -5% - 10%)
30% chance of 0.2 to 0.56 (~ 20% - 55%)
20% chance of 0.89 to 1.37 (~ 90% - 140%)
10% chance of 2.1 (~ 200%)
что-то похожее на вероятности, которые были у другого человека. Теперь для вашей функции это намного сложнее, а другие ответы почти наверняка более применимы и элегантны. Но ты все еще можешь это сделать.
расположите каждую из букв в порядке их вероятности - от наибольшей до наименьшей. Затем получите их совокупные суммы, начиная с 0, без последнего. (таким образом, вероятности 50%, 30%, 20% становятся 0, 0.5, 0.8). Затем вы умножаете их до тех пор, пока они не станут целыми числами с разумной точностью (0, 5, 8). Затем постройте их-ваши кумулятивные вероятности - это ваши x, вещи, которые вы хотите выбрать с заданной вероятностью (ваши буквы), - это ваши Y. (вы, очевидно, не можете построить фактические буквы на оси y, поэтому вы просто построите их индексы в некотором массиве). Затем вы попытаетесь найти там некоторую регрессию, и это будет ваша функция. Например, попробовав эти цифры, я получил
e^0.14x - 1
и это:
let letters: [Character] = ["a", "b", "c"]
func randLetter() -> Character {
return letters[Int(exp(0.14 * Double(arc4random_uniform(10))) - 1)]
}
возвращает " a " 50% времени," b "30% времени и" c " 20% времени. Очевидно, довольно громоздко для большего количества букв, и потребуется некоторое время, чтобы выяснить правильную регрессию, и если вы хотите изменить Весы, вам нужно сделать это вручную. Но если ты ... --25-->сделал найдите хорошее уравнение, которое соответствовало бы вашим значениям, фактическая функция будет иметь только пару строк в длину и быстро.
Это кажется хорошей возможностью для бесстыдного подключения к моей небольшой библиотеке, swiftstats: https://github.com/r0fls/swiftstats
например, это будет генерировать 3 случайные величины из нормального распределения со средним 0 и дисперсией 1:
import SwiftStats
let n = SwiftStats.Distributions.Normal(0, 1.0)
print(n.random())
поддерживаемые распределения включают: нормальный, экспоненциальный, биномиальный и т. д...
Он также поддерживает подгонку выборочных данных к заданному распределению, используя оценку максимального правдоподобия для распределение.
см. проект readme для получения дополнительной информации.