看看这个问题:
Swift probability of random number being selected?
最佳答案建议使用switch语句来完成工作。但是,如果我需要考虑大量案例,那么代码看起来非常不优雅;我有一个巨大的switch语句,在每种情况下一遍又一遍地重复使用非常相似的代码。
当您有大量概率要考虑时,是否有一种更好,更清晰的方式来选择具有一定概率的随机数? (比如~30)
答案 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”。对于更多的字母来说显然非常麻烦,并且需要一段时间来找出正确的回归,如果你想改变权重,你必须手动完成。但是如果你 找到一个适合你的值的好方程,那么实际的函数只会是几行,而且很快。