无心在维基看到了一个关于几率悖论的讨论Boy or Girl paradox。有争议的的题目以下:
史密斯先生有两个孩子,至少其中之一是男孩,请问两个孩子都是男孩的可能性有多大?
原文以下:
Mr. Smith has two children. At least one of them is a boy. What is the probability that both children are boys?
一部分人的答案是1/3,一部分人的答案是1/2.为何会产生这样的结果呢,文中说得很清楚是由于问题自己存在歧义,对“至少一个孩子是男孩”的信息作不一样的假设,致使了不一样的结果。问题归根结底是因为天然语言的二义性,将问题转化成了两个不一样的数学模型,从而产生了两个不一样的结果。咱们知道设计一种编程语言的任务之一就是消除其二义性,所以通常而言编程语言是一种没有歧义的语言(因此程序员都喜欢写代码不喜欢说话么),若是用编程语言来描述这一问题,会不会好理解点呢?以java为例,咱们来看看。
如下是对史密斯先生有两个孩子的可能状况进行描述,其中random.nextBoolean()函数随机返回true或false的几率均为1/2,用于模拟现实中生男孩女孩的几率各一半。可见程序不只描述了全部的组合,还明确了题目中暗含的条件。java
class TwoChildren { Child child1; Child child2; public TwoChildren()//一个孩子是男孩或女孩的几率是50% { child1 = random.nextBoolean()?Child.BOY:Child.GIRL; child2 = random.nextBoolean()?Child.BOY:Child.GIRL; } }
接着描述“至少一个孩子是男孩”,由于这里是存在歧义的地方,因此转化成代码描述时,咱们会根据两个不一样的假定,获得不一样的代码。1/3结果的假定条件:观察了史密斯的两个孩子,其中一个是男孩:程序员
boolean isObserved(TwoChildren draw) { return draw.child1 == Child.BOY || draw.child2 == Child.BOY; }
熟悉java的程序员会注意到,若是child1为BOY的话,child2不会被观察(||运算符后面的代码不会被执行),是否和天然语言描述不同?咱们从逻辑或运算定义可知两者是等价的,即便||运算符后面的代码被执行,也不影响程序结果,换成天然语言就是咱们观察史密斯的两个孩子时,一个一个的观察,若是发现其中一个是男孩,就已经保证了“至少一个是男孩”,就不必接着观察了。
接着描述1/2的结果假定条件:随机观察了史密斯的一个孩子,其中一个是男孩:编程
boolean isObserved(TwoChildren draw) { return random.nextBoolean()?draw.child1 == Child.BOY:draw.child2 == Child.BOY; }
其中random.nextBoolean()函数随机返回true或false的几率均为1/2,就是两个孩子被选中观察的几率是1/2.dom
最后咱们描述问题的提出编程语言
if(isObserved(draw)) { observedCount ++; if(draw.child1 == Child.BOY && draw.child2 == Child.BOY) {//两个孩子都是男孩 matchedCount++; //通过不少次运算后,((double)matchedCount/observedCount)最可能的值是多少? } }
至此咱们将一个以天然语言描述的问题,转化成了一个以程序语言描述的问题。这个转化是否正确呢,咱们作10万次测试,看结果是否知足数学推导的预期。如下是一个完整的测试代码:ide
package hermitdl.test2; import java.util.Random; /** * Test for <<Boy or Girl paradox>> */ public class App { static enum Child { BOY, GIRL }; static Random random = new Random(); static class TwoChildren { Child child1; Child child2; public TwoChildren()//一个孩子是男孩或女孩的几率是50% { child1 = random.nextBoolean()?Child.BOY:Child.GIRL; child2 = random.nextBoolean()?Child.BOY:Child.GIRL; } } //随机检查一个孩子的性别是不是男孩 static class PeekOneTest extends ProbabilityTest { @Override boolean isObserved(TwoChildren draw) { return random.nextBoolean()?draw.child1 == Child.BOY:draw.child2 == Child.BOY; } } //检查两个孩子的性别,是否其中之一是男孩 static class PeekTwoTest extends ProbabilityTest { @Override boolean isObserved(TwoChildren draw) { return draw.child1 == Child.BOY || draw.child2 == Child.BOY; } } static abstract class ProbabilityTest { int observedCount = 0; int matchedCount = 0; //在isObserved为真的状况下计数,以及两个孩子均是女孩的状况计数。 void test(TwoChildren draw) { if(isObserved(draw)) { observedCount ++; if(draw.child1 == Child.BOY && draw.child2 == Child.BOY) { ////两个孩子都是男孩 matchedCount++; } } } abstract boolean isObserved(TwoChildren draw); void printResult() { System.out.printf(this.getClass().getSimpleName() +"=%d/%d=%f\n" ,matchedCount ,observedCount ,((double)matchedCount/observedCount)); } } public static void main( String[] args ) { PeekOneTest peekOneTest = new PeekOneTest(); PeekTwoTest peekTwoTest = new PeekTwoTest(); TwoChildren draw; for(int i = 0;i < 1000000; i++) { draw = new TwoChildren(); peekOneTest.test(draw); peekTwoTest.test(draw); } peekOneTest.printResult(); peekTwoTest.printResult(); } }
如下为程序的几回运行结果:
PeekOneTest=249436/499301=0.499570
PeekTwoTest=249436/750234=0.332478函数
PeekOneTest=250209/500229=0.500189
PeekTwoTest=250209/749846=0.333681测试
PeekOneTest=249963/500234=0.499692
PeekTwoTest=249963/749712=0.333412this
可见模拟结果始终分别在1/2与1/3附近波动,是符合数学预期的。设计