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

时间:2015-05-18 17:34:40

标签: ios swift coding-style

看看这个问题:

Swift probability of random number being selected?

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

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

4 个答案:

答案 0 :(得分:21)

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

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

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

答案 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中执行相同的操作。

答案 2 :(得分:1)

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

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

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

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

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

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

答案 3 :(得分: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”。对于更多的字母来说显然非常麻烦,并且需要一段时间来找出正确的回归,如果你想改变权重,你必须手动完成。但是如果你 找到一个适合你的值的好方程,那么实际的函数只会是几行,而且很快。