程序设计方法学:
理解自顶向下的设计和自底向上的执行
方法论:理解并掌握一批python程序设计思维实践能力
学会编写更有设计感的程序
“体育竞技分析”问题分析:
需求:毫厘是多少,如何科学分析体育竞技比赛
输入:球员的水平
输出:可预测的比赛成绩
体育竞技分析:模拟N场比赛
计算思维:抽象+自动化
模拟:抽象比赛过程+自动化执行N场比赛
当N越大时,比赛结果分析会越科学
比赛规则:双人击球比赛:A&B,回合制,5局3胜
开始时一方先发球,直至判分,接下来胜者发球
球员只能在发球局得分,15分胜一局。
解决这个问题,我们可以采用自顶向下(解决复杂问题的有效方法)的设计方法和自底向上(逐步组建复杂系统的有效测试方法)的执行方法。
自顶向下:
基本含义:将一个总问题表达为若干个小问题组成的形式
使用同样方法进一步分解小问题
直至小问题可以用计算机简单明了的解决。
自底向上(执行)逐步组建复杂系统的有效测试方法
分单元测试,逐步组装
按照自顶向下相反的路径操作
直至系统各部分以组装的思路都经过测试和验证。
第一阶段:程序总体框架及步骤
步骤一:打印程序的介绍性信息式
步骤二:获得程序运行参数:proA(球员A能力值)、proB(球员B能力值)、n(比赛场次){从用户那里得到的数据}
步骤三:利用球员A和球员B的能力值,模拟n局比赛(计算机)
步骤四:输出球员A和球员B获胜比赛的场次及概率
我们根据自顶向下的设计,确定以下函数:
main()函数包括printInfo()(打印输出信息)、getInputs()(将获取的球员A能力值、球员B能力值、比赛场次提供给调用这个函数的main()函数)、simNGames() (模拟n局比赛)、printSummary()(从main1函数获取比赛结果后进行输出)
我们设置的代码如下:
printInfo函数:介绍性内容,提高用户体验
def printInfo():
print("这个程序模拟两个选手A和B的某种竞技比赛")
print("程序运行需要A和B的能力值(以0到1之间的小数表示)")
getInputs()(将获取的球员A能力值、球员B能力值、比赛场次提供给调用这个函数的main()函数)
def getInputs():
a=eval(input("请输入选手A的能力值(0-1):"))
b=eval(input("请输入选手B的能力值(0-1):"))
n=eval(input("模拟比赛的场次"))
return a,b,n #(分别返回一个元组类型,对应三个输入)
这是用户所输入的信息。
printSummary()(从main1函数获取比赛结果后进行输出)
def printSummary(winsA,winsB):
n=winsA+winsB
print("竞技分析开始,共模拟{}场比赛".format(n))
print("选手A获胜{}场比赛,占比{:0.1%}".format(winsA,winsA/n))
print("选手B获胜{}场比赛,占比{:0.1%}".format(winsB,winsB/n))
主函数为:
def main():
printInfo()
probA,probB,n=getInputs()
winsA,winsB=simNGames(n,probA,probB)
printSummary(winsA,winsB)
之后我们对模拟n局比赛的函数代码模块进行设计。要求解模拟n局比赛的函数模块,我们可以先设计模拟一局比赛的代码,之后让它们循环n次即可。在面对设计一局比赛的代码的时候,我们可以先设计比赛结束的代码。如下所示:
def gameOver(a,b):
return a=15 or b=15
之后我们设计一局比赛的代码simOneGame():
def simOneGame(probA,probB):
scoreA,scoreB=0,0
serving="A"
while not gameOver(scoreA,scoreB):
if serving="A":
if random()<probA
scoreA+=1
else:
serving="B"
else:
if random()<probB
scoreB+=1
else:
serving="A"
return scoreA,scoreB
设计思路如下:首先对于一局比赛来说,,初始得分全部为0,我们根据规则可知,只有在发球局才可以得分。当球员A发球的时候,面对比赛如果没有结束的话,我们要进行如下循环,如果发球的是A,当随机取得值比A的能力值小,则A加分,如果随机取得所要求的能力值比A大,则球员B发球。对于球员B发球的时候,如果随机取得能力值比球员B小,则球员B加分,如果随机取得能力值比B大,则球员A发球。当有一方球员达到15分的时候,这局比赛结束,即循环结束,之后返回球员A和球员B的得分情况。
之后我们设计n局比赛的代码simNGames():此类函数表示的是模拟N场比赛获得结果
def simNGames(n,probA,probB):
winsA,winsB=0,0
for i in range(n):
scoreA,scoreB=simOneGame(probA,probB)
if scoreA>scoreB:
winsA+=1
else:
winsB+=1
return winsA,winsB
我们在以上设计出了一局比赛的得分情况,当达到15分的时候,一局比赛结束。之后,我们设计N局比赛得分的情况,我们需要循环n局比赛,如果在调用一局比赛的simOneGame()函数之后,得分球员A的得分比球员B的大,则球员A赢一局,反之则球员B赢一局,之后返回球员A和球员B所赢的球的局数。
我们在设计过程中,首先将体育竞技分析这个大问题拆分为四个小问题,即打印介绍性信息,获得输入信息,模拟比赛结果,打印输出信息,还有总函数。我们进行逐步解决代码编写。之后,对于模拟N局比赛这个小问题里面,我们把他看成一个大问题,继续拆分为模拟一局比赛这个小问题。模拟N局比赛相当于N次模拟一局比赛,将总问题分解为模拟一局比赛并且将它循环N次。之后,我们利用规则将一局比赛的代码进行编写。即利用接口将函数联系起来,最后即可编写完成。
以上即为我们整体的代码,我们在代码段末尾调用main()函数即可运行,即输入main( )。
整体代码如下所示:
from random import random
def printInfo():
print("这个程序模拟两个选手A和B的某种竞技比赛")
print("程序运行需要A和B的能力值(以0到1之间的小数表示)")
def getInputs():
a=eval(input("请输入选手A的能力值(0-1):"))
b=eval(input("请输入选手B的能力值(0-1):"))
n=eval(input("模拟比赛的场次:"))
return a, b, n # (分别返回一个元组类型,对应三个输入)
def printSummary(winsA,winsB):
n=winsA+winsB
print("竞技分析开始,共模拟{}场比赛".format(n))
print("选手A获胜{}场比赛,占比{:0.1%}".format(winsA,winsA/n))
print("选手B获胜{}场比赛,占比{:0.1%}".format(winsB,winsB/n))
def gameOver(a,b):
return a==15 or b==15
def simOneGame(probA, probB):
scoreA, scoreB = 0, 0
serving = "A"
while not gameOver(scoreA, scoreB):
if serving == "A":
if random() < probA:
scoreA += 1
else:
serving = "B"
else:
if random() < probB:
scoreB += 1
else:
serving = "A"
return scoreA, scoreB
def simNGames(n,probA,probB):
winsA,winsB=0,0
for i in range(n):
scoreA,scoreB=simOneGame(probA,probB)
if scoreA>scoreB:
winsA+=1
else:
winsB+=1
return winsA,winsB
def main():
printInfo()
probA,probB,n=getInputs()
winsA,winsB=simNGames(n,probA,probB)
printSummary(winsA,winsB)
main()
运行界面如下所示:

举一反三:
理解自顶向下和自底向上
理解自顶向下的设计思维:分而治之
理解自底向上的执行思维:模块化集成
自顶向下是“系统思维”的简化
应用问题的扩展:
扩展比赛参数,增加对更多能力对比情况的判断
扩展比赛设计,增加对真实比赛结果的预测
扩展分析逻辑,反向推理,用胜率推算能力。
