程序员的概率分布

这是我最近遇到的这些非常简单的编程难题之一: 给定一个函数以相等的概率返回04随机整数,编写一个返回06随机整数的函数。
当然,解决方案还应该返回均布的数字。 因此,让我们从输入函数示例定义开始:

def rand4() = (math.random * 5).toInt

你的任务是实现rand6()只用rand4() 给自己几分钟,继续阅读。

第一种方法非常简单,但是碰巧被彻底打破了:

def rand6() = rand4() * 3 / 2

就如此容易。 在理想的解决方案中,应该以1/7的概率出现从06每个输出值。 您能否从上面的代码中看出rand6()返回25的概率是rand6() ? 没错,它不超过0 ,您永远不会得到这些值。 我希望很清楚为什么。 因此,让我们去做一些更复杂的事情:

def rand6() = (rand4() + rand4()) % 7

看起来更好,但仍然很远。 上面的代码有两个主要缺陷。 首先, rand4() + rand4()表达式的结果范围是08但是我们需要06 。 显而易见的解决方案是使用% 7操作。 但是,这导致01返回的频率是两倍,因为78溢出到01 。 那么呢:

def rand6(): Int = {
    val rand8 = rand4() + rand4()
    if(rand8 > 6)
        rand6()
    else
        rand8
}

我希望递归(可以很容易地转换成循环,但我将工作留给Scala编译器)不会掩盖意图–如果两个rand4()调用的总和超出预期结果,我们只需丢弃它并调用rand6()再次。 但是,仍然存在一个细微但引人注目的错误,引用Wikipedia的统一分布 。 两个独立的,均匀分布的均匀分布之和产生对称的三角形分布 。 如果您还不了解以上内容,请使用<canvas/>演示一下Wikipedia的含义, 以JavaScript演示此实时演示

该程序只是将像素放置在每个面板上的随机 (X, Y)位置。 在第一个面板中,我使用了一个Math.random() * 300调用,该调用按比例缩放以适合整个画布。 如您所见,分布或多或少是均匀的。 但是我们不能说第二和第三面板。 在第二个面板上,我原则上使用两个均匀分布的变量之和: (Math.random() + Math.random()) * 150) 。 即使此表达式可以返回0300之间的任何0 ,这些点还是非常偏向于画布的中间(三角形分布)。 在第三个面板上强调了相同的行为,其中使用了十个Math.random()调用。

正确答案

我采用的方法基于rand4()能够产生两个随机的最低有效位的观察。 因此,让我们从使用已知语义实现rand3()开始:

def rand3(): Int = rand4() match {
    case 4 => rand3()
    case x => x
}

rand3()通过拒绝rand4() 4输出来返回从03均匀分布值。 那将如何帮助我们? 好了,我们现在有两个随机位,每个位是01 ,概率为50%。 我们可以轻松地将其扩展为更大的序列,例如rand15()rand7()

def rand15() = (rand3() << 2) + rand3()
def rand7() = rand15() >> 1

您应该对上面的摆弄感到相当自在。 具有产生两个随机位的能力,我可以轻松地生成4和3。现在rand6()rand6()的事情:

def rand6() = rand7() match {
    case 7 => rand6()
    case x => x
}

为了使本课程更加有趣,让我们在randN(n: Int)之上实现randN(n: Int) rand4()randN()应该返回从0n均匀分布自然值。 首先,我将实现辅助方法atLeastKrandBits(k: Int)返回…… 至少K个随机位

def atLeastKrandBits(k: Int): Int = k match {
    case 0 => 0
    case 1 => rand3() >> 1
    case 2 => rand3()
    case b => (atLeastKrandBits(k - 2) << 2) + rand3()
}

foldLeft() 替代实现

def atLeastKrandBits(k: Int) = (0 to (k + 1) / 2).foldLeft(0){
    (acc, _) => (acc << 2) + rand3()
}

…或者如果您真的讨厌那些人来维护您的代码:

def atLeastKrandBits(k: Int) = (0 /: (0 to (k + 1) / 2)){
    (acc, _) => (acc << 2) + rand3()
}

具有randN(n: Int)以上的任何实现randN(n: Int)简单:

def randN(n: Int) = {
    val bitsCount = java.lang.Integer.highestOneBit(n)
    val randBits = atLeastKrandBits(bitsCount)
    if(randBits > n)
        randN(n)
    else
        randBits
}

结论

您可能会问自己一个问题: 我为什么还要关心? 如果您不了解概率分布,则您的应用程序可能会产生实际上很容易预测的随机输出。 如果您正在编写游戏,并且敌人更有可能出现在地图上的某些位置上,那没什么大不了的(但是玩家发现并滥用它!)但是如果出于安全原因需要随机数,或者您依赖统一分配例如,出于负载平衡目的–任何偏差都可能致命。

参考: Java和社区博客中JCG合作伙伴 Tomasz Nurkiewicz 为程序员提供的概率分布

翻译自: https://www.javacodegeeks.com/2013/01/probability-distribution-for-programmers-2.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值