Генерировать случайные числа с заданным распределением

проверьте этот вопрос:

быстрая вероятность выбора случайного числа?

верхний ответ предлагает использовать оператор 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 для получения дополнительной информации.