Python for Kids
  • 0 前言
  • 1 编程环境准备
  • 2 运算符和表达式
  • 3 掌握变量
  • 4 字符串
  • 5 获取用户的输入
  • 6 条件判断
  • 7 条件判断实操
  • 8 FOR循环
  • 9 循环和列表
  • 10 WHILE循环
  • 11 WHILE循环实操
  • 12 WHILE循环再实操
  • 13 多重循环
  • 14 再谈列表
  • 15 初见函数
  • 16 函数实操
  • 17 选择排序
  • 18 冒泡排序
  • 19 递归算法之一
  • 20 递归算法实操
  • 21 快速排序
  • 22 汉诺塔游戏
  • 23 递推算法
  • 24 分治算法
  • 25 集合与组合
  • 26 贪心算法
  • 27 字典和键值对
  • 28 广度优先搜索算法
  • 29 数组和向量化计算
  • 30 随机和模拟
  • 31 数据可视化
  • 32 文件读取和分析
Powered by GitBook
On this page
  • 大纲
  • 什么是随机
  • 骰子的模拟
  • 硬币的模拟
  • 练习

Was this helpful?

30 随机和模拟

上帝在扔骰子吗

大纲

  • 什么是随机模拟

  • 对骰子的模拟

  • 对硬币的模拟

  • 练习

什么是随机

生活中我们经常遇到碰运气这个说法,这是因为某些事件具有随机性。所谓随机性是指某个事件的结果可能是这样也可能是那样,我们在事前并不能预测到。例如我们往桌上扔一个硬币,硬币可能正面朝上,也可能反面朝上,在扔这个硬币之前我们是没办法预测到具体的结果的。这种具有随机性的事件我们叫做随机事件。

虽然随机事件的单次结果我们无法预测,但是当这种随机事件发生的次数非常多,有一些基本规律还是可以得到的。还是以扔硬币为例,单次扔硬币的结果无法预测,但是如果说硬币是正常的,扔很多次下来,应该正面和反面的出现次数各占一半左右,这种规律我们是可以得到的。类似扔硬币这种事件,出现正反面结果所占的比率叫做概率,有专门的概率论来研究各种概率,不过我们这次并不会深入学习概率,我们只是以程序模拟的角度来初步尝试一下。

骰子的模拟

计算机可以很好的来模拟一些随机事件,例如掷骰子。在前一节我们学习了python中的numpy模块,它有一个子模块,叫做random,可以用来做各种随机的模拟和计算。我们先将模块加载进来

import numpy as np
import  numpy.random as rd

random模块中有一个很有趣的函数就是randint,它可以用来随机生成一些整数,使用的时候,需要指定产生随机整数的两边的边界,如果我们要模拟一次掷骰子的结果,假设骰子只有6个面,各自数字是从1到6,那么我们需要在函数中定义好,最小值是1,最大值7,因为最大值并不会出现在生成结果中,所以真实生成的数字就是1到6.

rd.randint(1,7)
3

另一种生成1到6的随机数字的方法是使用choice函数,它可以从一个列表或数组中随机选择一下元素。

number = [1,2,3,4,5,6]
rd.choice(number)
2

如果是想模拟生成多个随机数字,可以在函数后面加一个输入参数,表示生成的个数,例如我们想扔骰子连续扔10次,就得到下面的结果,可以看到模拟的结果是一个数组

dies = rd.randint(1,7,10)
dies
array([4, 6, 3, 3, 3, 1, 1, 6, 5, 5])

也可以用choice来模拟连续扔骰子

number = [1,2,3,4,5,6]
dies = rd.choice(number,10)
dies
array([2, 1, 5, 3, 5, 5, 2, 4, 6, 5])

计算机是不知疲惫的,我们可以扔10次,干嘛不扔一万次呢,我们可以扔一万次,将结果存到dies变量中

dies = rd.randint(1,7,10000)

我们想看一下扔一万次骰子的结果到底是怎么样的,可以数一下各自点数出现的次数,也就是说1点出现多少次,2点出现多少次,等等。我们可以写一个函数来计算

def func_count(x):
    result = dict()
    number = np.unique(x)
    for n in number:
        result[n] = x[x == n].size
    return result
func_count(dies)
{1: 1696, 2: 1679, 3: 1635, 4: 1687, 5: 1649, 6: 1654}

在上面这个函数中,用一个字典来存放最终的结果,先用unique来计算这些生成数组中的去重值,也就是1到6这六个点数,然后用一个循环去遍历观察,每个点数出现的次数,放入字典中。从结果上来看,各种点数出现的次数不完全一样,但是也差不多,说明点数的出现是平均的。

一个骰子的点数是平均出现的,那么2个骰子的点数之和也是平均的吗?我们可以模拟多个骰子,例如下面我们来试试2个骰子

die_1 = rd.randint(1,7,10000)
die_2 = rd.randint(1,7,10000)
die_sum = die_1 + die_2
func_count(die_sum)
{2: 262,
 3: 578,
 4: 799,
 5: 1121,
 6: 1398,
 7: 1618,
 8: 1467,
 9: 1082,
 10: 844,
 11: 563,
 12: 268}

可以看到这些点数存在一些规律,在点数较小和点数较大的区域,如2点,12点,只有2百多次,7点出现的最多,有1618次,这是为什么呢?因为2点出现的条件是两个骰子都是等于1,这种事件出现的机会比较小,而7点出现的条件就很多了,2+5也可以是7,3+4也可以是7,所以7点出现的机会比较大。我们可以把上面的函数稍微改一下,计算出各点数出现的次数,相对于总次数的占比,这个占比就是概率,它衡量了某个点数出现的机会。

def func_prob(x):
    result = dict()
    number = np.unique(x)
    for n in number:
        result[n] = x[x == n].size/len(x)
    return result
func_prob(die_sum)
{2: 0.0262,
 3: 0.0578,
 4: 0.0799,
 5: 0.1121,
 6: 0.1398,
 7: 0.1618,
 8: 0.1467,
 9: 0.1082,
 10: 0.0844,
 11: 0.0563,
 12: 0.0268}

研究了骰子之和,我们再换个问题,扔两个骰子,扔出两个5都朝上的概率是多少?

test_num = 10000
die_1 = rd.randint(1,7,test_num)
die_2 = rd.randint(1,7,test_num)
result = (die_1 == 5) & (die_2 == 5)
sim_prob = result.sum()/test_num
print("模拟概率为{x:.4f}".format(x=sim_prob))
模拟概率为0.0284

让我们换个思路,如果不用计算机程序来计算,而是用数学的思路来思考,某个骰子点数等于5出现的概率应该是1/6,那另一个骰子点数等于5出现的概率为是1/6,所以直接相乘,这就得到了真实的概率。

real_prob = (1/6)*(1/6)
print("真实概率为{x:.4f}".format(x=real_prob))
真实概率为0.0278

小结:可以使用numpy.random模块中的一些函数来模拟一些随机事件,从而计算出模拟的概率值,从扔骰子的例子看出,当数据量较大的时候,模拟的概率值和真实的概率会非常接近。

硬币的模拟

玩完了骰子,我们来玩一下硬币,先提出一个问题,如果抛三枚硬币,三枚硬币都是正面或反面的概率是多少?

模拟硬币的代码和骰子是类似的,我们用1来表示硬币的反面,1来表示硬币的正面,可以写出类似的程序,得到三个硬币各扔一万次的结果。

test_num = 10000
coin_1 = rd.randint(0,2,test_num)
coin_2 = rd.randint(0,2,test_num)
coin_3 = rd.randint(0,2,test_num)

这些模拟的结果都是数组的形式,计算数组是否满足某个条件,直接使用==比较操作符,再用&操作符,表示“且”的关系,这样计算出三个硬币都是正面或都是反面的模拟结果,

result_1 = (coin_1 == 1) & (coin_2 == 1) & (coin_3 == 1)
result_2 = (coin_1 == 0) & (coin_2 == 0) & (coin_3 == 0)
(result_1.sum()+result_2.sum())/test_num
0.2527

我们再来看一个稍微复杂一点的问题: 假设有人找你玩一个游戏,每人一个硬币,每次两人一起给出正反面

如果: 1. 出现两个正面,你赢 3 块钱 2. 出现两个反面,你赢 1 块钱 3. 出现一正一反,你输 2 块钱

问题是你要不要和他去玩这个游戏?

如果玩这个游戏可以不亏钱,应该就可以玩,先假设两个人给出正反面都是随机给出的,可以模拟一下看看。

test_num = 10000
coin_1 = rd.randint(0,2,test_num)
coin_2 = rd.randint(0,2,test_num)
result_1 = (coin_1 == 1) & (coin_2 == 1)
result_2 = (coin_1 == 0) & (coin_2 == 0)
result_3 = ((coin_1 == 0) & (coin_2 == 1)) | ((coin_1 == 1) & (coin_2 == 0))

可以看到这三种可能的结果,对应的概率大约是1/4,1/4,1/2

print(result_1.sum())
print(result_2.sum())
print(result_3.sum())
2550
2431
5019

然后我们把各结果对应的收益考虑进去,就是各收益直接乘上对应的结果,再求和。

amt_result = 3*result_1+1*result_2 +(-2)*result_3
amt_result.sum()
43

看起来没有亏,可以玩起来,但是要注意一点,我们的假设是双方的硬币是随机出正反,有可能对方的硬币并不是随机出现正反的,如果对方一直出现反面会怎么样?

test_num = 10000
coin_1 = rd.randint(0,2,test_num)
coin_2 = np.zeros(test_num)
result_1 = (coin_1 == 1) & (coin_2 == 1)
result_2 = (coin_1 == 0) & (coin_2 == 0)
result_3 = ((coin_1 == 0) & (coin_2 == 1)) | ((coin_1 == 1) & (coin_2 == 0))
amt_result = 3*result_1+1*result_2 +(-2)*result_3
amt_result.sum()
-4991

看起来我们就大亏了,你可以想一想,如果对手使用了一直出反面的策略,我们应该怎么做?

练习

基于前述的知识,利用python来写一个抽扑克牌的程序,也就是尝试从一副牌中随机抽取两张牌,计算抽得一对A的概率是多少。然后和真实的概率值进行对比。

下面我们用数组来建立一副扑克,数组中每个元素实际上是一个字典,s表示了花色,n表示了点数。

suit = ['红心','黑桃','方片','草花']
number = ['2','3','4','5','6','7','8','9','10','J','Q','K','A']
cards = np.array([{'s':s,'n':n} for s in suit for n in number])
cards[:4]
array([{'s': '红心', 'n': '2'}, {'s': '红心', 'n': '3'},
       {'s': '红心', 'n': '4'}, {'s': '红心', 'n': '5'}], dtype=object)

可以使用choice来从数组中模拟随机抽牌。

my_card = rd.choice(cards, 2,replace=False)
my_card
array([{'s': '黑桃', 'n': '10'}, {'s': '红心', 'n': 'J'}], dtype=object)

下面来写一个函数,计算抽得一对A的概率是多少

def cards_func():
    my_card = rd.choice(cards, 2,replace=False)
    is_AA = my_card[0]['n'] == 'A' and my_card[1]['n'] == 'A'
    return is_AA
test_num = 10000
result = np.array([cards_func() for x in range(test_num)])
sim_prob = result.sum()/test_num
print("模拟概率为{x:.4f}".format(x=sim_prob))
模拟概率为0.0047

抽得一对A的理论概率可以这样来思考,52张牌中只有4张A,那么要抽一对A的过程可以分为两步,第一步先从52中抽到4张A的一张,这一步的概率是4/52,第二步是从剩下的51张牌中抽3张A中的一张,这一步概率是3/51。

real_prob = (4/52)*(3/51)
print("真实概率为{x:.4f}".format(x=real_prob))
真实概率为0.0045

Previous29 数组和向量化计算Next31 数据可视化

Last updated 4 years ago

Was this helpful?