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

问题描述 投票:7回答:4

看看这个问题:

Swift probability of random number being selected?

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

当您有大量概率需要考虑时,是否有一种更好,更清晰的方式来选择具有一定概率的随机数? (比如~30)

ios swift coding-style
4个回答
21
投票

这是一个受到Generate random numbers with a given (numerical) distribution的各种答案强烈影响的Swift实现:

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.2返回0,以概率0.3返回1,以概率0.5返回2。

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

以概率1/3返回0,以概率2/3返回1。


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

4
投票

当您有大量概率需要考虑时,是否有一种更好,更清晰的方式来选择具有一定概率的随机数?

当然。编写一个基于概率表生成数字的函数。这基本上就是你指向的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中执行相同的操作。


1
投票

对于我的小型图书馆swiftstats来说,这似乎是一个无耻插件的好机会:https://github.com/r0fls/swiftstats

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

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

支持的发行版包括:正常,指数,二项式等...

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

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


0
投票

您可以使用指数函数或二次函数 - 将x作为随机数,将y作为新的随机数。然后,你只需要摇动方程式,直到它适合你的用例。说我有(x ^ 2)/ 10 +(x / 300)。把你的随机数放入(作为一些浮点形式),然后在Int()出现时获得发言权。所以,如果我的随机数发生器从0变为9,我有40%的几率获得0,有30%的几率获得1 - 3,有20%的几率获得4 - 6,并有10%的几率获得你基本上试图伪造某种正态分布。

这里有一个关于它在Swift中会是什么样子的想法:

func giveY (x: UInt32) -> Int {
  let xD = Double(x)
  return Int(xD * xD / 10 + xD / 300)
}

let ans = giveY (arc4random_uniform(10))

编辑:

我上面不太清楚 - 我的意思是你可以用一些函数替换switch语句,这些函数将返回一组数字,其概率分布可以通过使用wolfram或其他东西的回归得出。因此,对于您链接的问题,您可以执行以下操作:

import Foundation

func returnLevelChange() -> Double {

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

}

newItemLevel = oldItemLevel * returnLevelChange()

所以该函数在-0.05和2.1之间返回一个double。这将是你的“x%差/比当前项目水平更好”的数字。但是,由于它是一个指数函数,它不会返回均匀的数字扩散。 arc4random_uniform(10)从0到9返回一个int,每个都会产生一个像这样的double:

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的每个int都有相同的显示机会,因此你得到如下概率:

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

}

50%的时间返回“a”,30%的时间返回“b”,20%的时间返回“c”。对于更多的字母来说显然非常麻烦,并且需要一段时间来找出正确的回归,如果你想改变权重,你必须手动完成。但如果你确实找到了一个适合你的值的好方程式,那么实际的函数只能是几行,而且速度很快。

© www.soinside.com 2019 - 2024. All rights reserved.