资讯专栏INFORMATION COLUMN

用程序消除一道概率题的二义性

JackJiang / 2941人阅读

摘要:以下是对史密斯先生有两个孩子的可能情况进行描述,其中函数随机返回或的概率均为用于模拟现实中生男孩女孩的概率各一半。

  无意在维基看到了一个关于概率悖论的讨论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,用于模拟现实中生男孩女孩的概率各一半。可见程序不仅描述了所有的组合,还明确了题目中暗含的条件。

 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.

最后我们描述问题的提出

if(isObserved(draw))
{
    observedCount ++;
    if(draw.child1 == Child.BOY && draw.child2 == Child.BOY)
    {//两个孩子都是男孩
        matchedCount++;
      //经过很多次运算后,((double)matchedCount/observedCount)最可能的值是多少?
    }
}

  至此我们将一个以自然语言描述的问题,转化成了一个以程序语言描述的问题。这个转化是否正确呢,我们做10万次测试,看结果是否满足数学推导的预期。以下是一个完整的测试代码:

package hermitdl.test2;

import java.util.Random;

/**
 * Test for <>
 */
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
"
            ,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.333412

  可见模拟结果始终分别在1/2与1/3附近波动,是符合数学预期的。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/19836.html

相关文章

  • 程序消除一道概率题的义性

    摘要:以下是对史密斯先生有两个孩子的可能情况进行描述,其中函数随机返回或的概率均为用于模拟现实中生男孩女孩的概率各一半。   无意在维基看到了一个关于概率悖论的讨论Boy or Girl paradox。有争议的的题目如下:  史密斯先生有两个孩子,至少其中之一是男孩,请问两个孩子都是男孩的可能性有多大?  原文如下:  Mr. Smith has two children. At leas...

    littlelightss 评论0 收藏0
  • [Java] 关于一道面试题的思考

    摘要:对于这种会退出的情况,数组显然不能像链表一样直接断开,因此采用标记法先生成一个长度为的布尔型数组,用填充。中对整个进行遍历才能得到此时数组中的数量。 文中的速度测试部分,时间是通过简单的 System.currentTimeMillis() 计算得到的, 又由于 Java 的特性,每次测试的结果都不一定相同, 对于低数量级的情况有 ± 20 的浮动,对于高数量级的情况有的能有 ± 10...

    rozbo 评论0 收藏0
  • 精读《手写 SQL 编译器 - 文法介绍》

    摘要:一般用大写的表示文法的开头,称为开始符号。更多讨论讨论地址是精读手写编译器文法介绍如果你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。 1 引言 文法用来描述语言的语法规则,所以不仅可以用在编程语言上,也可用在汉语、英语上。 2 精读 我们将一块语法规则称为 产生式,使用 Left → Right 表示任意产生式,用 Left => Right 表示产生式的推导过程,比如对...

    TNFE 评论0 收藏0
  • 一篇算法讲解的注解

    摘要:前言从公式到算法之前的完整路径应该是数学公式中文公式中文算法英文算法偶然看到一篇算法文章,讲解了百度校园招聘之编程题的核心算法思路,我根据它又整理出自己的解题思路。 前言 从公式到算法之前的完整路径应该是:数学公式->中文公式->中文算法->英文算法 偶然看到一篇算法文章,讲解了百度2016校园招聘之编程题的核心算法思路,我根据它又整理出自己的解题思路。 第一题 题目在原文中可以找到,...

    fevin 评论0 收藏0

发表评论

0条评论

JackJiang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<