万事皆可变,万事亦皆有概率,而偏偏是日常生活中无比普遍的它,有时能创造出一个奇迹。蒲丰投针就恰恰体现了这一点。
一根小小的针,也可以投出整个宇宙。
蒲丰投针实验,本是我们伟长七下的一次数学实验课,课后我将它深化了一下,作为一个编程课题,即“用Python
模拟蒲丰投针”。这里用一篇博文,记录一下这次实验课中我的探究成果。
注:写作这篇文章时,我已经八上结束了,故在一些细节可能会有记忆上的偏差,还请谅解。
1. 何谓“蒲丰投针”?
取一张白纸,在上面画上许多条间距为$a$的平行线。
取一根长度为$l(l≤a)$的针,随机地向画有平行直线的纸上掷$n$次,观察针与直线相交的次数,记为$m$。
计算针与直线相交的概率.
布丰本人证明了,这个概率是:
由于它与$\pi$有关,于是人们想到利用投针试验来估计圆周率的值。
布丰发现:有利的扔出与不利的扔出两者次数的比,是一个包含$\pi$的表示式.如果针的长度等于$a/2$,那么扔出的概率为$1/\pi$.扔的次数越多,由此能求出越为精确的$\pi$的值。
2. 纯输出版本与实验的简化
在着手写带有GUI
的版本前,我们不妨先对这个实验进行一些简化。我们可以假设这些间距相等的直线都无限长,故这时我们没有必要再去模拟针的x
坐标(因为它可以从0取到正无穷,继而没有意义)与纸的宽度,只需要模拟y
坐标即可。
2.1 程序初始化
1 | # -*-Coding:UTF-8-*- |
这一段中我们定义了一个列表和一个辅助函数。我这里定义纸的长度为120单位,lineYlist
中以12单位为间隔确定了所有的直线y
坐标。inRange()
这个函数用于判断某个值n
是否在start
和end
之间(且无需考虑start
和end
的大小关系,可以取等),这在后面会很有用。
2.2 定义Pin
类与计算y
坐标
如果针的长度等于$a/2$,那么扔出的概率为$1/\pi$.
为了方便计算,我们针的长度取12的一半,6。我们这个Pin
类会生成两个参数,y
坐标和掉落角度angle
,储存在self.coords
这个变量里。
1 | class Pin: |
具体定义如下:
这里对angle
的定义为:
根据一点简单的三角函数,我们可以知道:
所以通过y1
计算y2
的方法就是(这里用了一开始定义的inRange
函数:
1 | if inRange(self.angle, 0, 90): |
所以完整的__init__()
函数就如下:
1 | class Pin: |
2.3 碰撞检测
在这个简化版的程序里面,我们只要写一个循环,判断这根针的y1
和y2
是否包含lineYlist
中的某一项即可。
1 | def checkCollide(self, y1: int, y2: int) -> bool: |
我这里用的方法可能有点笨,用一个和lineYlist
长度相同的列表来分别记录是否跟lineYlist
中的每一项碰撞。最后再判断这个列表中是否有True
(且理论上来说,只能有一个)即可。
2.4 主循环与完整代码
1 | # -*-Coding:UTF-8-*- |
最后的话我们就循环10次,每次”投掷“100000根针并判断是否碰撞并计数。最后用次数100000除以碰撞次数即可。据测试,这种方法的精确度在十分位(即精确到3.1
)。
3. 绘图版本的蒲丰投针
在这个版本里会更加复杂,既要模拟x
坐标,也要模拟y
坐标,且同时要判断碰撞和出界。绘图我先是用tkinter
写了一遍,后来用pygame
重写了一遍(而且比较复杂)。
3.1 tkinter
版本
1 | # -*-Coding:UTF-8-*- |
其实基本的逻辑和纯输出版本差不多,只是需要初始化窗口且每一个元素都需要绘制,所以步骤多了一些。
3.2 Pygame
版本
1 | import pygame, math, random, time |
这个Pygame
版本我个人觉得写的比较臃肿,由于为了避免掉绘制出界的针且因为Pygame
和tkinter
在主循环逻辑上的不同(tkinter
可以边运行变更新画面,Pygame
一定要全计算好再整体绘制),我先循环直到生成1000根合适的针,再绘制,但也没有找到更简单的方法。
虽然这个程序还有一定的改进空间,但课程已经结束,我也不想再去改了(至少我逻辑是对的)。不过我还是想明白了一点,在写任何程序之前,一定要先捋清楚自己的思路,不然直接上手写,会很混乱、很复杂,甚至达不到自己想要的效果。
THE END感谢您的阅读~