生成具有给定分布的随机数

Generate random numbers with a given distribution

看看这个问题:

Swift probability of random number being selected?

最佳答案建议使用 switch 语句来完成这项工作。但是,如果我要考虑的情况非常多,代码看起来就很不优雅;我有一个巨大的 switch 语句,在每种情况下都一遍又一遍地重复非常相似的代码。

当您要考虑大量概率时,是否有更好、更简洁的方法来以一定概率选择随机数? (大约 30 个)

Is there a nicer, cleaner way to pick a random number with a certain probability when you have a large number of probabilities to consider?

当然可以。编写一个函数,根据 table 的概率生成一个数字。这基本上就是您所指向的 switch 语句:代码中定义的 table 。您可以使用定义为概率和结果列表的 table 对数据执行相同的操作:

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.

为了清楚起见,我在这里保留了 table 的缩写,但是您可以根据需要设置它,并且可以在数据文件中定义它,这样您就不必当列表更改时重新编译。

请注意,关于此方法,Swift 没有任何特别之处——您可以在 C 或 Swift 或 Lisp 中做同样的事情。

这是一个 Swift 实施,受到各种 Generate random numbers with a given (numerical) distribution.

的答案

对于 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)
}

示例:

let x = randomNumber(probabilities: [0.2, 0.3, 0.5])

returns 0 概率为 0.2,1 概率为 0.3, 和 2 概率为 0.5.

let x = randomNumber(probabilities: [1.0, 2.0])

return 0 的概率为 1/3,1 的概率为 2/3。


对于Swift3/Xcode8:

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)
}

对于Swift2/Xcode7:

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)
}

您可以使用指数函数或二次函数来实现 - 将 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))

编辑:

我在上面不是很清楚 - 我的意思是你可以用一些函数替换 switch 语句,该函数将 return 一组具有概率分布的数字,你可以使用 wolfram 进行回归计算或者其他的东西。因此,对于您链接到的问题,您可以这样做:

import Foundation

func returnLevelChange() -> Double {

  return 0.06 * exp(0.4 * Double(arc4random_uniform(10))) - 0.1

}

newItemLevel = oldItemLevel * returnLevelChange()

所以函数 return 是一个介于 -0.05 和 2.1 之间的双精度值。那就是你的 "x% worse/better than current item level" 数字。但是,由于它是一个指数函数,它不会 return 均匀分布数字。 arc4random_uniform(10) return 是一个从 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,你想要 select 给定概率(你的字母)的东西是你的 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)]

}

returns "a" 50% 的时间,"b" 30% 的时间,以及 "c" 20% 的时间。显然对于更多的字母来说相当麻烦,并且需要一段时间才能找出正确的回归,如果你想改变权重,你必须手动进行。但是,如果您 确实 找到了一个适合您的值的方程式,那么实际的函数将只有几行长且快速。

这似乎是一个无耻地插入我的小图书馆 swiftstats 的好机会: https://github.com/r0fls/swiftstats

例如,这将从均值为 0 方差为 1 的正态分布生成 3 个随机变量:

import SwiftStats
let n = SwiftStats.Distributions.Normal(0, 1.0)
print(n.random())

支持的分布包括:正态分布、指数分布、二项分布等...

它还支持使用分布的最大似然估计器将样本数据拟合到给定分布。

有关详细信息,请参阅项目自述文件。