我认为你基本上走在正确的轨道上。可能存在精度或范围问题,但一般来说,如果您想随机选择 3、2、1 或 0,并且您希望选择 3 的概率是选择 0 的概率的四倍,那么如果是一个纸质练习,你可能会在一个充满的网格上:
3 3 3 3
2 2 2
1 1
0
把东西扔到它上面,然后读出它落在的数字。
您所需的线性比例的选项数量为:
- 1 if number of options, n, = 1
- 1 + 2 if n = 2
- 1 + 2 + 3 if n = 3
- ... etc ...
它是算术级数的简单总和。您最终会得到 n(n+1)/2 个可能的结果。例如,对于 n = 1,这是 1 * 2 / 2 = 1。对于 n = 2,这是 2 * 3 /2 = 3。对于 n = 3,这是 3 * 4 / 2 = 6。
因此,您将立即编写如下内容:
NSUInteger random_linear(NSUInteger range)
{
NSUInteger numberOfOptions = (range * (range + 1)) / 2;
NSUInteger uniformRandom = arc4random_uniform(numberOfOptions);
... something ...
}
那时你只需要决定uniformRandom属于哪个bin。最简单的方法是使用最明显的循环:
NSUInteger random_linear(NSUInteger range)
{
NSUInteger numberOfOptions = (range * (range + 1)) / 2;
NSUInteger uniformRandom = arc4random_uniform(numberOfOptions);
NSUInteger index = 0;
NSUInteger optionsToDate = 0;
while(1)
{
if(optionsToDate >= uniformRandom) return index;
index++;
optionsToDate += index;
}
}
鉴于您可以在不迭代的情况下计算 optionsToDate,一个明显更快的解决方案是二分搜索。
一种更聪明的看待它的方法是,uniformRandom 是从 (0, 0) 到 (n, n) 的线下方的框的总和。所以它是图形下方的区域,图形是一个简单的直角三角形。所以你可以从面积公式倒推。
具体来说,图下方从 (0, 0) 到 (n, n) 在位置 x 处的区域为 (x*x)/2。因此,您正在寻找 x,其中:
(x-1)*(x-1)/2 <= uniformRandom < x*x/2
=> (x-1)*(x-1) <= uniformRandom*2 < x*x
=> x-1 <= sqrt(uniformRandom*2) < x
在这种情况下,您想取 x-1,因为结果没有进展到数字网格的下一个离散列。因此,您可以通过平方根运算简单的整数截断到达那里。
因此,假设我一路上没有混淆我的确切不等式,并假设所有精度都合适:
NSUInteger random_linear(NSUInteger range)
{
NSUInteger numberOfOptions = (range * (range + 1)) / 2;
NSUInteger uniformRandom = arc4random_uniform(numberOfOptions);
return (NSUInteger)sqrtf((float)uniformRandom * 2.0f);
}