查看全课大纲(第 36 / 51 节)
  1. 1.为什么人人都该学点编程?
  2. 2.计算机与程序设计:你的电脑到底在“想”什么?
  3. 3.Python 这门语言,到底有什么魔力?
  4. 4.Python 开发环境配置:如何拥有你的第一个“编程工具箱”?
  5. 5.如何安装一个更强大的代码编辑器?
  6. 6.汇率兑换1.0:你的第一个“钱生钱”小程序
  7. 7.你的第一行代码,藏着哪些秘密?
  8. 8.汇率兑换2.0(上):让程序看懂“CNY”和“USD”
  9. 9.汇率兑换程序,如何让它“聪明”地判断币种?
  10. 10.汇率兑换程序能一直运行,直到我喊停吗?
  11. 11.汇率兑换4.0:如何把“算钱”功能打包成积木?
  12. 12.汇率兑换5.0:给你的程序穿上“结构化”的西装
  13. 13.怎么用 Python 画一个会动的五角星?
  14. 14.如何让程序自动画出不同大小的五角星?
  15. 15.函数自己调用自己,会发生什么?
  16. 16.用递归画出神奇的分形树
  17. 17.Python 怎么计算你一天躺着不动能消耗多少热量?
  18. 18.如何让程序“活”起来,持续为你服务?
  19. 19.如何让 BMR 计算器更“聪明”?
  20. 20.当程序遇到“不按套路出牌”的用户怎么办?
  21. 21.怎么用 Python 模拟一个“滚雪球”式的存钱计划?
  22. 22.如何用列表记录每一周的存款?
  23. 23.如何让程序自己数数?—— 用 for 循环解放你的双手
  24. 24.52周存钱挑战4.0:你的存款计算器能有多灵活?
  25. 25.52周存钱挑战:怎么根据日期自动查存款?
  26. 26.Python 怎么判断一年中的第几天?
  27. 27.列表 vs 元组:哪个更适合存储月份天数?
  28. 28.用集合给月份分个队,换个思路算天数
  29. 29.把月份和天数“锁”在一起:用字典给数据配个对
  30. 30.Python 怎么判断密码强弱?
  31. 31.Python 怎么给密码设置“防沉迷”系统?
  32. 32.Python 怎么把密码保存到文件里?
  33. 33.Python 怎么读取文件里的密码?
  34. 34.Python 怎么把密码检查工具“打包”起来?
  35. 35.Python 怎么把文件操作也“打包”起来?
  36. 36.计算机怎么“扔骰子”?——用Python模拟随机事件
  37. 37.如何用 Python 模拟掷两个骰子?
  38. 38.怎么用 Python 把骰子轨迹画出来?
  39. 39.骰子点数分布长啥样?用直方图一眼看穿!
  40. 40.用科学计算库让骰子程序“起飞”
  41. 41.空气质量指数怎么算?手把手教你写一个AQI计算器
  42. 42.空气质量指数计算:从零开始,让程序看懂空气
  43. 43.如何读取空气质量数据文件并找出空气质量最好的五个监测点?
  44. 44.JSON 数据怎么变成 Excel 表格?—— 让数据换个“马甲”
  45. 45.Python 怎么让程序自己判断文件类型?
  46. 46.如何让程序自己上网查空气质量?
  47. 47.用美味的汤来解析网页:Beautiful Soup 入门
  48. 48.如何让爬虫自动抓取全国所有城市的空气质量?
  49. 49.爬到的数据怎么存下来?—— 把全国空气质量数据保存为CSV文件
  50. 50.用 pandas 玩转空气质量数据:找出全国空气最好和最差的城市
  51. 51.空气质量数据怎么“洗”干净,还能画成图?

计算机怎么“扔骰子”?——用Python模拟随机事件

约 21 分钟

小象实战讲义 · Python 入门:8 个小项目边玩边学

你有没有想过,计算机这个“死板”的家伙,怎么模拟现实生活中的随机事件?比如扔骰子、抽奖、或者游戏里的随机掉落?

今天我们就来玩一个有趣的项目:让计算机帮我们扔骰子!而且不是扔一次两次,是扔成千上万次,然后统计每个点数出现的次数和频率。你会发现,随着扔的次数越来越多,每个点数出现的频率会越来越接近1/6——这就是概率论里“大数定律”的直观体现!

学完这一节,你就能写出一个程序,让它帮你模拟扔骰子,看看“运气”到底是不是公平的。

💡 核心导读

  • 🎲 项目目标:模拟扔一个骰子,统计各点数出现的次数和频率
  • 🎯 本节新增:只学一个关键技能——用Python生成随机数
  • 🔧 核心工具:random模块(Python的“随机数生成器”)
  • 📊 数据统计:用列表记录结果,用循环统计频率
  • 🔍 观察规律:扔的次数越多,结果越接近理论概率

一、骰子与随机数:计算机的“不确定性”

在现实生活中,扔一个正常的骰子,每次结果都是随机的——可能是1,可能是6,也可能是其他点数。但计算机程序是确定的,同一段代码每次运行结果都一样,怎么模拟这种“不确定性”呢?

答案就是:随机数生成器。

Python提供了一个非常方便的模块叫做random(英文就是“随机”的意思),它就像是一个虚拟的骰子,每次调用都能“摇”出一个随机数。

random模块的常用功能

让我们先来认识一下这个神奇的模块。在Python中,使用一个模块前需要先“引入”它:

# 引入random模块
import random

引入之后,我们就可以使用random提供的各种函数了。下面是一些最常用的:

import random
random.seed(42)  # 固定随机种子,让示例输出可复现

# 1. random():生成0到1之间的随机小数(包含0,不包含1)
print("0~1的随机小数:", random.random())
print("再运行一次:", random.random())

# 2. uniform(a, b):生成a到b之间的随机小数
print("10~100的随机小数:", random.uniform(10, 100))

# 3. randint(a, b):生成a到b之间的随机整数(包含a和b)
print("1~6的随机整数:", random.randint(1, 6))
print("再扔一次骰子:", random.randint(1, 6))

# 4. choice(list):从列表中随机选一个元素
colors = ['红', '黄', '蓝', '绿']
print("随机颜色:", random.choice(colors))

# 5. shuffle(list):打乱列表的顺序
numbers = [1, 2, 3, 4, 5, 6]
random.shuffle(numbers)
print("打乱后的列表:", numbers)

运行上面的代码,你会得到下面这一组结果。注意代码第 2 行写了 random.seed(42):它把随机数“固定”在同一个起点,所以你每次运行都会看到完全一样的输出(另外 shuffle 还会直接改变原列表)。这是课程为了让大家对照而做的固定。

0~1的随机小数: 0.6394267984578837
再运行一次: 0.025010755222666936
10~100的随机小数: 34.75263865322073
1~6的随机整数: 2
再扔一次骰子: 2
随机颜色: 红
打乱后的列表: [4, 2, 3, 1, 5, 6]

小提示:现在因为有 seed(42),你的结果会和上面一模一样。随机数的本性其实是“每次不同”——把 random.seed(42) 这一行删掉再运行,每次就都会得到不同的新结果了。

二、模拟扔一个骰子

现在我们知道怎么生成随机数了,接下来就正式模拟扔骰子。一个标准的骰子有6个面,点数从1到6,所以我们需要生成1到6之间的随机整数。

第一步:定义“扔骰子”的函数

为了让代码更清晰,我们先把“扔骰子”这个动作封装成一个函数:

import random
random.seed(42)  # 固定随机种子,让示例输出可复现

def roll_dice():
    """
    模拟扔骰子
    返回1到6之间的随机整数
    """
    roll = random.randint(1, 6)
    return roll

# 测试一下
print("第一次扔:", roll_dice())
print("第二次扔:", roll_dice())
print("第三次扔:", roll_dice())

运行结果:

第一次扔: 6
第二次扔: 1
第三次扔: 1

看,我们的虚拟骰子已经能正常工作了!每次调用roll_dice()函数,就相当于扔了一次骰子。

第二步:扔多次并记录结果

只扔一次没什么意思,我们想看看扔很多次会是什么结果。比如扔10次、100次,甚至10000次。

我们需要一个“记录本”来记录每个点数出现的次数。Python的列表(list)非常适合这个任务:

import random
random.seed(42)  # 固定随机种子,让示例输出可复现

def roll_dice():
    """模拟扔骰子"""
    return random.randint(1, 6)

# 设置扔骰子的总次数
total_times = 10

# 初始化记录列表:6个0,分别对应点数1~6出现的次数
# [0, 0, 0, 0, 0, 0] 表示:1出现0次,2出现0次,...,6出现0次
result_list = [0] * 6

# 开始扔骰子!
for i in range(total_times):
    # 扔一次骰子
    roll = roll_dice()
    
    # 根据点数更新记录
    # 方法1:用if判断(直观但有点啰嗦)
    if roll == 1:
        result_list[0] += 1
    elif roll == 2:
        result_list[1] += 1
    elif roll == 3:
        result_list[2] += 1
    elif roll == 4:
        result_list[3] += 1
    elif roll == 5:
        result_list[4] += 1
    elif roll == 6:
        result_list[5] += 1

print("各点数出现的次数:", result_list)

运行结果:

各点数出现的次数: [3, 3, 1, 0, 0, 3]

这个结果表示:在10次投掷中,点数1出现了3次,点数2出现了3次,点数3出现了1次,点数4出现了0次,点数5出现了0次,点数6出现了3次。列表下标0到5依次对应点数1到6,次数加起来 3+3+1+0+0+3=10,正好等于投掷总次数。

第三步:优化统计方法

上面的代码用了6个if语句,虽然能工作,但有点重复。我们可以用循环来简化:

import random
random.seed(42)  # 固定随机种子,让示例输出可复现

def roll_dice():
    """模拟扔骰子"""
    return random.randint(1, 6)

total_times = 10
result_list = [0] * 6

for i in range(total_times):
    roll = roll_dice()
    
    # 用循环判断是哪个点数
    # j从1到6,分别对应6个可能的点数
    for j in range(1, 7):  # range(1, 7)生成1,2,3,4,5,6
        if roll == j:
            # 注意:列表索引从0开始,所以点数j对应索引j-1
            result_list[j - 1] += 1
            break  # 找到匹配的点数后就可以跳出内层循环了

print("各点数出现的次数:", result_list)
各点数出现的次数: [3, 3, 1, 0, 0, 3]

这样代码就更简洁了。不过换成 20 面骰时不止改一处:randint(1, 6) 的右端点要改成 20,统计列表的长度也要跟着变——result_list = [0] * 6 要改成 [0] * 20(否则第 7~20 点没格子计数,还会下标越界)。两处要配套修改,这是初学者最容易漏的地方。

三、显示详细的统计结果

现在我们有了每个点数出现的次数,但输出格式不够友好。我们想要这样的效果:

点数1的次数:2,频率:0.20
点数2的次数:1,频率:0.10
...

同时获取索引和值:enumerate()函数

在遍历列表时,我们通常只能获取到元素的值。但如果同时需要索引号(位置)和值,该怎么办呢?

Python提供了一个很方便的函数:enumerate()(英文是”枚举”的意思)。看个例子:

# 普通遍历:只能获取值
fruits = ['苹果', '香蕉', '橙子']
for fruit in fruits:
    print(fruit)

print("---")

# 使用enumerate:同时获取索引和值
for i, fruit in enumerate(fruits):
    print(f"第{i}个水果是:{fruit}")

运行结果:

苹果
香蕉
橙子
---
第0个水果是:苹果
第1个水果是:香蕉
第2个水果是:橙子

enumerate()返回的是一个个元组(tuple),每个元组包含两个元素:索引和值。我们用i, fruit分别接收这两个值。

完善我们的统计输出

现在用enumerate()来改进骰子统计程序:

import random
random.seed(42)  # 固定随机种子,让示例输出可复现

def roll_dice():
    """模拟扔骰子"""
    return random.randint(1, 6)

total_times = 1000  # 这次扔1000次,看得更清楚
result_list = [0] * 6

# 扔骰子并统计
for i in range(total_times):
    roll = roll_dice()
    for j in range(1, 7):
        if roll == j:
            result_list[j - 1] += 1
            break

# 用enumerate输出详细结果
print(f"扔骰子{total_times}次的结果:")
for i, count in enumerate(result_list):
    # i是索引(0~5),对应点数i+1
    # count是该点数出现的次数
    frequency = count / total_times  # 计算频率
    print(f"点数{i+1}的次数:{count},频率:{frequency:.3f}")  # :.3f表示保留3位小数

运行结果:

扔骰子1000次的结果:
点数1的次数:166,频率:0.166
点数2的次数:172,频率:0.172
点数3的次数:162,频率:0.162
点数4的次数:165,频率:0.165
点数5的次数:160,频率:0.160
点数6的次数:175,频率:0.175

注意:代码固定了 random.seed(42),所以你的结果会和上面完全一样;删掉这一行再运行,每次才会不同,但无论怎么变,各点数的频率都在 0.166 左右。

四、观察概率的魔力

让我们做一个有趣的实验:分别扔10次、100次、1000次、10000次,看看频率有什么变化。

import random
random.seed(42)  # 固定随机种子,让示例输出可复现

def roll_dice():
    """模拟扔骰子"""
    return random.randint(1, 6)

def simulate_dice_rolls(times):
    """模拟扔指定次数的骰子并返回结果"""
    result_list = [0] * 6
    
    for i in range(times):
        roll = roll_dice()
        for j in range(1, 7):
            if roll == j:
                result_list[j - 1] += 1
                break
    
    return result_list

# 测试不同次数
test_times = [10, 100, 1000, 10000]

for total_times in test_times:
    print(f"\n扔骰子{total_times}次:")
    results = simulate_dice_rolls(total_times)
    
    for i, count in enumerate(results):
        frequency = count / total_times
        print(f"  点数{i+1}: {count}次 ({frequency:.3f})")

运行结果示例:


扔骰子10次:
  点数1: 3次 (0.300)
  点数2: 3次 (0.300)
  点数3: 1次 (0.100)
  点数4: 0次 (0.000)
  点数5: 0次 (0.000)
  点数6: 3次 (0.300)

扔骰子100次:
  点数1: 19次 (0.190)
  点数2: 17次 (0.170)
  点数3: 18次 (0.180)
  点数4: 11次 (0.110)
  点数5: 15次 (0.150)
  点数6: 20次 (0.200)

扔骰子1000次:
  点数1: 161次 (0.161)
  点数2: 169次 (0.169)
  点数3: 159次 (0.159)
  点数4: 173次 (0.173)
  点数5: 169次 (0.169)
  点数6: 169次 (0.169)

扔骰子10000次:
  点数1: 1636次 (0.164)
  点数2: 1598次 (0.160)
  点数3: 1637次 (0.164)
  点数4: 1702次 (0.170)
  点数5: 1671次 (0.167)
  点数6: 1756次 (0.176)

发现规律了吗?

  • 扔10次时,频率波动很大(这一组里从 0 到 0.3 都有)
  • 扔100次时,波动变小了
  • 扔1000次时,基本都在0.166左右
  • 扔10000次时,非常接近理论值1/6 ≈ 0.1667

这就是大数定律的直观体现:随着试验次数增加,频率会越来越接近理论概率。对于一个均匀的骰子,每个点数出现的概率应该是1/6 ≈ 16.67%。

五、完整程序与固定种子

为了让你的运行结果和讲义一致(方便对照),我们可以给随机数生成器设置一个“固定种子”。这样每次运行都会产生相同的随机序列。

import random

# 设置随机种子,让每次运行结果一致
random.seed(42)  # 42是个常用的魔法数字,你可以改成任何整数

def roll_dice():
    """模拟扔骰子"""
    return random.randint(1, 6)

def main():
    """主函数"""
    total_times = 10000
    
    # 初始化记录列表
    result_list = [0] * 6
    
    # 扔骰子并统计
    for i in range(total_times):
        roll = roll_dice()
        for j in range(1, 7):
            if roll == j:
                result_list[j - 1] += 1
                break
    
    # 输出结果
    print(f"扔骰子{total_times}次的结果:")
    for i, count in enumerate(result_list):
        frequency = count / total_times
        print(f"点数{i+1}的次数:{count},频率:{frequency:.4f}")
    
    # 理论值
    print(f"\n理论频率:{1/6:.4f}")

# 运行程序
main()

运行结果(固定种子为42时):

扔骰子10000次的结果:
点数1的次数:1618,频率:0.1618
点数2的次数:1629,频率:0.1629
点数3的次数:1647,频率:0.1647
点数4的次数:1687,频率:0.1687
点数5的次数:1671,频率:0.1671
点数6的次数:1748,频率:0.1748

理论频率:0.1667

实际应用时:去掉random.seed(42)这行,这样每次运行都会得到真正的随机结果。

📝 动手练一练

  1. 修改骰子面数:如果骰子不是6面,而是20面(点数1~20),该怎么修改程序?试试看!

  2. 统计最大值:在扔完骰子后,找出出现次数最多的点数和最少的点数。

  3. 验证均匀性:计算所有点数的频率与理论频率(1/面数)的绝对误差之和,这个值越小说明骰子越均匀。

👉 点击查看参考答案

1. 20面骰子的修改思路:

import random
random.seed(42)  # 固定随机种子,让示例输出可复现

def roll_dice(sides=6):
    """模拟扔骰子,默认6面"""
    return random.randint(1, sides)

total_times = 1000
sides = 20  # 20面骰子
result_list = [0] * sides  # 创建20个元素的列表

for i in range(total_times):
    roll = roll_dice(sides)
    result_list[roll - 1] += 1  # 直接使用点数作为索引

for i, count in enumerate(result_list):
    frequency = count / total_times
    print(f"点数{i+1}的次数:{count},频率:{frequency:.3f}")
点数1的次数:45,频率:0.045
点数2的次数:46,频率:0.046
点数3的次数:57,频率:0.057
点数4的次数:51,频率:0.051
点数5的次数:43,频率:0.043
点数6的次数:42,频率:0.042
点数7的次数:49,频率:0.049
点数8的次数:67,频率:0.067
点数9的次数:64,频率:0.064
点数10的次数:49,频率:0.049
点数11的次数:48,频率:0.048
点数12的次数:34,频率:0.034
点数13的次数:44,频率:0.044
点数14的次数:58,频率:0.058
点数15的次数:56,频率:0.056
点数16的次数:49,频率:0.049
点数17的次数:40,频率:0.040
点数18的次数:63,频率:0.063
点数19的次数:48,频率:0.048
点数20的次数:47,频率:0.047

2. 统计最常出现和最不常出现的点数:

import random

# 模拟数据(实际使用时用真实模拟结果)
result_list = [168, 158, 176, 170, 162, 166]  # 1000次模拟的结果

max_count = max(result_list)
min_count = min(result_list)
max_point = result_list.index(max_count) + 1
min_point = result_list.index(min_count) + 1

print(f"最常出现的点数:{max_point},出现了{max_count}次")
print(f"最不常出现的点数:{min_point},出现了{min_count}次")
最常出现的点数:3,出现了176次
最不常出现的点数:2,出现了158次

3. 验证均匀性:

import random
random.seed(42)  # 固定随机种子,让示例输出可复现

total_times = 10000
sides = 6
result_list = [0] * sides

# 模拟扔骰子(简化版)
for i in range(total_times):
    roll = random.randint(1, sides)
    result_list[roll - 1] += 1

# 计算总误差
theory_freq = 1 / sides
total_error = 0

for count in result_list:
    actual_freq = count / total_times
    error = abs(actual_freq - theory_freq)  # 绝对误差
    total_error += error

print(f"理论频率:{theory_freq:.4f}")
print(f"总绝对误差:{total_error:.4f}")
print(f"平均每个点数的误差:{total_error/sides:.4f}")
理论频率:0.1667
总绝对误差:0.0212
平均每个点数的误差:0.0035

本章小结

今天我们完成了一个很有趣的项目:用计算机模拟扔骰子。通过这个项目,你学到了:

  • 随机数的生成:使用random模块生成各种随机数
  • 函数的封装:把”扔骰子”这个功能封装成roll_dice()函数
  • 列表的统计:用列表记录每个点数出现的次数
  • enumerate的使用:在遍历时同时获取索引和值
  • 概率的观察:随着试验次数增加,频率趋近理论概率

最重要的是,我们只引入了一个新知识点:random模块的使用。其他都是之前项目中学过的内容(函数、列表、循环、条件判断)的巩固和组合。

📋 行动清单

学完这一节,你可以:

  • 运行完整程序,观察扔10000次骰子的结果
  • 修改骰子面数,试试20面骰子或100面骰子
  • 思考应用场景:随机数还能用在哪些地方?(抽奖、游戏、随机密码…)
  • 挑战自己:如果不使用enumerate(),你能用其他方法输出带点数的统计结果吗?

下一节,我们将升级这个项目:同时扔两个骰子!你会发现,两个骰子的点数之和的分布不再是均匀的,而是中间高、两边低的”钟形”分布。这又是为什么呢?我们下节揭晓!

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 第8章代码包:模拟掷骰子(含全部版本 5 个 .py)
    ZIP · 3KB
    下载
看图文没弄懂?展开看本节视频讲解(约 21 分钟)
📺 正在播放小象学院官方高清录播(支持倍速与清晰度调节)