生成具有给定分布的随机数
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())
支持的分布包括:正态分布、指数分布、二项分布等...
它还支持使用分布的最大似然估计器将样本数据拟合到给定分布。
有关详细信息,请参阅项目自述文件。
看看这个问题:
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())
支持的分布包括:正态分布、指数分布、二项分布等...
它还支持使用分布的最大似然估计器将样本数据拟合到给定分布。
有关详细信息,请参阅项目自述文件。