python自学之《Python程序设计 (第3版)》——模拟与设计

第 9 章 模 拟 与 设 计

9.1 模拟短柄壁球
9.1.1 一个模拟问题

Susan Computewell 的朋友 Denny Dibblebit 打短柄壁球。经过多年的比赛,他注意到了一个奇怪的现象。他经常与那些比他好一点点的球员竞争。在这个过程中,他似乎总是被击败,输掉了绝大多数的比赛。这导致他怀疑发生了什么。从表面来看,人们会认为那些“稍好”一点的球员应该“稍微”多赢一点,但对 Denny 而言,他们似乎赢得太多了。有一种明显的可能: Denny Dibblebit 的脑子有问题。也许他对自己水平的评估超过了他的身体素质。或许其他选手实际上比他好得多,他只是拒绝相信。

有一天,Denny 正在和 Susan 讨论短柄壁球,她提出另一种可能。也许这是游戏本身的性质,能力上的小差异会导致球场上的不平衡比赛。Denny 对这个想法感兴趣。如果没有帮助,他不希望在昂贵的运动心理学家那里浪费钱。但是,问题是精神上的还是比赛的一部分,他怎么知道呢?

Susan 说她可以编一个计算机程序来模拟短柄壁球的某些方面。利用模拟,他们可以让计算机在不同技能水平的选手之间模拟成千上万局比赛。由于不会有任何心理方面的问题,模拟将显示 Denny 是否输得比他的水平更多。 让我们自己来写短柄壁球模拟,看看 Susan 和 Denny 发现了什么。

9.1.2 分析与规格说明

短柄壁球是在两名球员之间使用球拍在四壁球场上打球的运动。它有一些方面类似于许多其他球类和球拍比赛,如网球、排球、羽毛球、壁球和乒乓球。我们不需要了解所有的短柄壁球规则才能写程序,只要理解比赛的基本情况。

要开始这个比赛,一名选手将球击出:这被称为“发球”。然后选手交替击球,使其保持比赛状态,这是一个“回合”。当其中一名选手未能有效击球时,对打结束。击球失误的选手输掉这一回合。如果输家是发球选手,则发球权转给另一名选手。如果发球选手赢得了这一回合,则会得 1 分。选手只能在自己发球时得分。第一名得到 15分的选手赢得比赛。在我们的模拟中,选手的能力水平将由选手在发球时赢得回合的概率来表示。因此,具有 0.6 概率的选手在 60%的发球回合中得分。该程序将提示用户输入两名选手的发球回合得分概率,然后用这些概率模拟多局短柄壁球比赛。然后程序将打印结果摘要。

9.2伪随机数

我们的模拟程序不得不处理不确定的事件。我们说选手赢得了 50%的发球回合,并不是说每隔一次发球回合都会得分。这更像是抛硬币。总的来说,我们预计半数时间硬币会正面向上、半数时间会反面向上,但没有什么可以阻止连续五次反面向上。同样,我们的短柄壁球选手也应该随机获胜或失败。发球回合得分概率提供了给定发球回合获胜的可能性,但没有设定的模式。

许多模拟都有这一特性,要求以事件某种可能性发生。驾驶模拟必须模拟其他驾驶员的不可预测性,银行模拟必须处理客户的随机到达。这些模拟有时被称为蒙特卡罗算法,因为结果取决于“机会”概率 。当然,你知道计算机没有什么是随意的,它们是指令执行机器。计算机程序如何模拟看似随机的事件呢?

模拟随机性是计算机科学中充分研究过的一个问题。还记得第 1 章的混沌程序吗?该程序产生的数字似乎在 0 和 1 之间随机跳动。这个明显的随机性来自反复应用函数来产生一系列数字。类似的方法可用于生成随机(实际上是“伪随机”)数字。

伪随机数发生器从某个“种子”值开始工作。该值被送入一个函数以产生“随机”数字。下次需要一个随机数时,将当前值反馈到该函数中以产生一个新的数字。通过仔细选择的函数,得到的值序列基本上是随机的。当然,如果你以相同的种子值重新启动该过程,那么最终会出现完全相同的数字序列。这一切都取决于生成函数和种子的值。

Python 提供了一个库模块,其中包含一些有用的函数来生成伪随机数。该模块中的函数根据模块加载的日期和时间推导出初始种子值,因此每次运行程序时都会获得不同的种子值。这意味着你也会获得唯一的伪随机值序列。我们最感兴趣的两个函数是 randrange 和random。

randrange 函数从给定范围中选择一个伪随机整数。它可以用一个、两个或三个参数,来确定一个范围,就像 range 函数一样。例如,randrange(1,6)从范围[1,2,3,4,5]中返回某个数字,而 randrange(5,105,5)返回 5~100 之间的 5 的倍数,包括边界。(回忆一下,范围向上直到,但不包括停止值。)

在这里插入图片描述

9.3自顶向下的设计
9.3.1 顶层设计
在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述9.3.2 关注点分离

在这里插入图片描述在这里插入图片描述

9.3.3 第二层设计

在这里插入图片描述在这里插入图片描述在这里插入图片描述

9.3.4 设计 simNGames
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述9.3.5 第三层设计
一切似乎都配合得很好。我们继续处理模拟的内部。下一个明显的攻击点是simOneGame。这里我们实际上必须对短柄壁球规则的逻辑进行编码。选手不断完成回合,直到游戏结束。这让人想到某种无限循环结构,我们不知道其中一名选手获得 15 分之前需要多少回合。循环只是持续下去,直到游戏结束。
在这个过程中,我们需要记录得分,我们还需要知道目前谁在发球。分数可能只是两个整数累积器,但是我们如何记录谁在发球?它是选手 A 或选手 B。一种方法是用存储“A”或“B”的字符串变量。它也是一种累积器,但更新它的值时,我们只是将它从一个值切换到另一个值。
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述在这里插入图片描述9.3.6 整理完成
在这里插入图片描述在这里插入图片描述

 17 def getInputs():
 18     # Returns the three simulation parameters
 19     a = float(input("What is the prob. player A wins a serve? "))
 20     b = float(input("What is the prob. player B wins a serve? "))
 21     n = int(input("How many games to simulate?"))
 22     return a,b,n
 23 
 24 def simNGames(n, probA, probB):
 25     # Simulates n games of racquetball between players whose
 26     # abilities are represented by the probability of winning a serve.
 27     # Returns number of wins for A and B
 28     winsA = winsB = 0
 29     for i in range(n):
 30         scoreA, scoreB = simOneGame(probA, probB)
 31         if scoreA > scoreB:
 32             winsA = winsA + 1
 33         else:
 34             winsB = winsB + 1
 35     return winsA,winsB
 36 
 37 def simOneGame(probA, probB):
 38     # Simulates a single game or racquetball between players whose
 39     # abilities are represented by the probability of winning a serve.
 40     # Returns final scores for A and B
 41     serving = "A"
 42     scoreA = 0
 43     scoreB = 0
 44     while not gameOver(scoreA, scoreB):
 45         if serving == "A":
 46             if random() < probA:
 47                 scoreA = scoreA + 1
 48             else:
 49                 serving = "B":
 50         else:
 51             if random() < probB:
 52                 scoreB = scoreB + 1
 53             else:
 54                 serving = "A"
 55     return scoreA, scoreB
 56 
 57 def gameOver(a, b):
 58     # a and b represent scores for a racquetball game
 59     # Return True if the game is over, False otherwise.
 60     return a==15 or b==15
 61 
 62 def printSummary(winsA, winsB):
 63     # Prints a summary of wins for each player.
 64     n = winsA + winsB
 65     print("\nGames simulated:", n)
 66     print("Wins for A: {0} ({1:0.1%})".format(winsA, winsA/n))
 67     print("Wins for B: {0} ({1:0.1%})".format(winsB, winsB/n))
 68 
 69 if __name__ == '__main__':main()

在这里插入图片描述

9.4自底向上的实现
9.4.1 单元测试

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

9.7练习

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值