Swift 是否有一些内置的方法来改变随机数的分布?我想使用线性方程来定义分布,例如 y = k * x + m。当 k = 0 时,所有数字应均匀分布。当 k = 1 时,分布应遵循该线,因此低 x 值将非常罕见,而高 x 值将很常见。我在 Excel 中四处游玩并尝试了不同的策略,最后想出了这段代码,它似乎有效——但在 Swift 中一定有更简洁的方法来做到这一点?
注意:首先我确实使用了 ClosedRange 数组而不是元组方法,然后使用了 .contains。然后我将其更改为元组数组,因为我的代码没有按预期工作。可能是另一个错误,但我仍然使用元组,因为代码现在可以工作了。
import Foundation
/* function to create an array of tuples with upper and lower
limits based on a linear distribution (y = k * x + m) */
func createDistributions(numbers: ClosedRange<Int>, k: Double) -> [(Double, Double)] {
var dist = [(Double, Double)]()
let m: Double = 0.5
let nVal: Int = numbers.count
var pStop: Double = 0.0
for x in numbers {
let t = (Double(x) + 0.5) / Double(nVal)
let y = (k * (t - 0.5) + m) * 2.0 / Double(nVal)
let start = pStop
let stop = y + start
dist.append((start, stop))
pStop = stop
}
return dist
}
// create distributions based on k-value of 1.0
var result = createDistributions(numbers: 0...34, k: 1.0)
// loop ten times, creating a Double random number each time
for _ in 0...9 {
let ran = Double.random(in: 0...1)
// check in which indexed array the random number belongs to by checking lower and upper limit
for i in 0..<result.count {
// the random number belongs to the i:th element, print i
if ran >= result[i].0 && ran <= result[i].1 {
print(i)
}
}
}
您的
y = kx+m
是一个 概率密度函数 (PDF)。将其应用于随机数生成的一个很好的方法是逆变换采样函数。我将逐步介绍如何开发它,以便您可以根据自己的特定需求进行调整。在一般情况下,这将通过一年级的微积分来完成,但对于线性情况,使用基本代数和一些小学几何就可以很容易地完成。对于这个例子,我将生成一个介于 0 和 1 之间的随机值。
(对于其他正在阅读的美国人:这是我们学习的斜率截距形式
y = mx+b
。请不要混淆 m
这里是截距,而不是斜率。我希望没有在任何地方混淆它们在这个答案中。)
要试验这个答案,请参阅图像来自的GeoGebra 工作表。
所有这一切的 TL;DR 是:
let u = Double.random(in: 0...1)
if k == 0 {
return u
} else {
return (sqrt(k*k + k*(8*u - 4) + 4) + k - 2)/(2*k)
}
但了解为什么这是答案才是真正的目标。
PDF 是一个函数,其两个 x 值之间的面积是该值介于这些值之间的概率。这导致 PDF 对于其范围内的所有值都必须为正,并且它下面的面积必须正好为 1(表示在整个范围内选择某个值的可能性为 100%)。
但是快速查看此曲线的任意版本表明它可能没有正确的区域:
对于
k
的给定值,存在有效的特定 m
值。我们可以通过根据 k
和 m
计算面积,将其设置为 1,然后求解 m
。图形的区域是一个以 1 为底(我们将选择的值的范围 0-1)和高度 m
的矩形,加上一个以 1 为底、高度为 k
的三角形。所以:
Area = Rectangle + Triangle = 1
m + k/2 = 1
m = 1 - k/2
代入 F(x):
F(x) = kx + 1 - k/2
我们还被限制
m
不能小于0,这将k
限制在[0,2]的范围内。当 k
为 0 时,所有值的可能性均等。当 k
为 2 时,值与其似然之间存在线性关系。
有了有效的 PDF,是时候创建一个 累积分布函数了。这是一个表示随机选择的值不大于给定值的可能性的函数。由于与 PDF 相同的原因,这些功能受到限制。它们必须在有效范围内从零单调增加到一。
这个面积可以像整个面积一样计算,通过将矩形和三角形相加:
CDF(x) = Rectangle + Triangle
= mx + (x/2 * (F(x) - m))
= ... some algebra later ...
= (k*x^2)/2 + (1-k/2)*x
注意这个函数正确地通过了 (0,0) 和 (1,1),因为它必须,并且在整个范围内都是正的。值不可能小于零,值小于或等于一的概率为 100%。
快到了。逆变换样本应用 CDF 的逆。这不是特别复杂,但代数很多,所以让 WolframAlpha 来做吧:
solve y = (k*x^2)/2 + (1-k/2)*x for x
==>
x = y and k = 0
x = -(sqrt(k^2 + 8 k y - 4 k + 4) - k + 2)/(2 k) and k!=0
x = (sqrt(k^2 + k (8 y - 4) + 4) + k - 2)/(2 k) and k!=0
对于 k=0,x=y。在其他地方,有两种解决方案。这里只有正值才有意义,所以请忽略负值。
红线是你想要的功能(这是k=1.5)。到达这里的路很长,但现在代码很简单:
// `k` ranges from 0 to 2, which is confusing. Map it to range 0...1
func randomValue(distribution d: Double) -> Double {
assert((0...1).contains(d))
let u = Double.random(in: 0...1)
// k ranges from 0 to 2
let k = d * 2
if k == 0 {
return u
} else {
return (sqrt(k*k + k*(8*u - 4) + 4) + k - 2)/(2*k)
}
}
只是为了测试一下:
func testRun(distribution d: Double) {
print("Distribution for \(d)")
let n = 10_000
// How many results begin with a given digit after the decimal point?
var h: [Substring:Int] = [:]
for _ in 0..<10_000 {
let value = randomValue(distribution: d)
let firstDigit = "\(value)".prefix(3).suffix(1)
h[firstDigit, default: 0] += 1
}
for digit in h.keys.sorted() {
let ratio = Double(h[digit]!)/Double(n)
print("\(digit) -> \(ratio.formatted(.percent.precision(.fractionLength(0))))")
}
}
testRun(distribution: 0)
testRun(distribution: 0.5)
testRun(distribution: 1)
===>
Distribution for 0.0
0 -> 10%
1 -> 10%
2 -> 10%
3 -> 11%
4 -> 10%
5 -> 10%
6 -> 10%
7 -> 10%
8 -> 10%
9 -> 10%
Distribution for 0.5
0 -> 6%
1 -> 6%
2 -> 7%
3 -> 9%
4 -> 10%
5 -> 11%
6 -> 11%
7 -> 13%
8 -> 13%
9 -> 14%
Distribution for 1.0
0 -> 1%
1 -> 3%
2 -> 5%
3 -> 7%
4 -> 9%
5 -> 11%
6 -> 13%
7 -> 15%
8 -> 17%
9 -> 19%
线性方程只能将其推到此为止。我不相信仅使用线性 PDF 就可以在低概率值和高概率值之间获得更大的差异(更好的数学家可能会在这里纠正我;这不是我的专长)。如果您愿意,我会探索将其应用于高阶多项式。除了
F(x) = kx + m
,您还可以使用 F(x) = kx^2 + m
或更高的幂来做同样的事情。这将需要一些第一年的微积分,但总体方法应该是相似的。