一、Github项目地址
https://github.com/000cxl000/arithmometer/tree/master
二、PSP
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
10 |
8 |
· Estimate |
· 估计这个任务需要多少时间 |
10 |
8 |
Development |
开发 |
660 |
800 |
· Analysis |
· 需求分析 (包括学习新技术) |
30 |
50 |
· Design Spec |
· 生成设计文档 |
30 |
40 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
10 |
15 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
5 |
5 |
· Design |
· 具体设计 |
40 |
60 |
· Coding |
· 具体编码 |
5h*60 |
7h*60 |
· Code Review |
· 代码复审 |
1h*60 |
2h*60 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
3h*60 |
5h*60 |
Reporting |
报告 |
290 |
330 |
· Test Report |
· 测试报告+博客 |
4h*60 |
5h*60 |
· Size Measurement |
· 计算工作量 |
10 |
20 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
35 |
70 |
合计 |
965 |
1423 |
三、项目要求
- 参与运算的操作数(operands)除了max_num(10)以内的整数以外,还要支持真分数的四则运算。操作数必须随机生成。
- 运算符(operators)为 +, −, ×, ÷ 运算符的种类和顺序必须随机生成。且每道题目中出现的运算符个数不超过5个。
- 使用 -n 参数控制生成题目的个数。
4. 生成的题目中计算过程不能产生负数。
5. 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。例如,23 + 45 = 和45 + 23 = 是重复的题目。
6. 在生成题目的同时,计算出所有题目的答案,并根据用户输入的结果进行判断对错,回答错误会有正确答案提示,并且统计得分。
四、解题思路
这个题目可以被划分为以下三个问题:
- 列出随机的四则运算表达式。
- 计算所列出四则运算的结果。
- 接受用户输入并比较结果是否正确,并在命令行中表现出来。
如何解决:
问题1:
表达式生成:
u 表达式的操作数比四则运算符数目多一个
u 操作数的个数可以随机
u 0 不能作为除数
u 计算过程中不能在某一步中出现负数(负数检测最终在计算器中实现)
利用列表先随机生成1~5个运算符,存入一个init_operators列表,通过列表长度生成对应个数的操作数,如果除号后的操作数等于0则重新生成一个,将操作数存入到另一个init_nums列表中。
操作数的类型统一为分数类型(Fraction类),整数的分母为1,但由于整数,带分数,真分数的显示格式不同,所以定义了一个str_num()的方法将分数转换为符合标准的字符串。
括号的插入:
插空法在运算符数组中随机成对插入括号,通过检测前后的运算符优先级判断是否需要插入括号。
问题2:
通过将字符串表达式转换成后缀表达式和栈的操作实现表达式的优先级区分和具体计算。(具体的四则运算集成在Fraction类中不需要自己实现)
问题3:
判断用户输入情况只需接受用户输入比较统计得分即可。由于要求采用命令行界面完成,该部分主要需要控制及美化命令行界面。
五、代码说明
1.项目文件结构如下:
模块 |
功能 |
main.py |
主函数 |
stack.py |
栈实现 |
ariExpression.py |
生成表达式 |
infixTosuffix.py |
将中缀表达式转成后缀表达式 |
Calculate.py |
计算 |
核心函数为ariExpression(生成随机表达式)和Calculate(计算结果)
ariExpression类
class Ari_Expression():
'''算术表达式的生成'''
def __init__(self, max_num):
'''生成运算符'''
self.init_operators()
'''生成随机数字'''
self.init_nums(max_num)
'''生成表达式'''
self.init_expression()
def init_num(self, max_num):
'''随机生成数'''
denominator = random.randint(1, max_num)#分母
numerator = random.randint(0, max_num)#分子
return Fraction(numerator, denominator)#通过Fraction类产生一个分数
def insert_bracket(self):
'''插入括号'''
bracket = ['(', ')']
n=len(self.operators)
if n > 1:
x = random.randint(0, n-2)
while x < n-2:
y = random.randint(x, n-2)
low=False
for a in self.operators[x:y+1]:
if a in ['+', '-']:
low = True
break
if self.operators[y] in ['×', '÷'] and low:
self.operators.insert(x, '(')
self.operators.insert(y+2,')')
x = y+2
def init_operators(self):
'''随机生成一个运算符并随机插入括号'''
self.operators = []
operator = ['+', '-', '×', '÷']
for x in range(5):
if x == 1:
self.operators.append(random.choice(operator[:-2]))
else:
y = random.choice(operator)
if y != 'null':
self.operators.append(y)
self.insert_bracket()
def init_nums(self, max_num):
self.nums = []
self.nums.append(self.init_num(max_num))
for x in range(len(self.operators)):
y = self.init_num(max_num)
if self.operators[x] == '÷':
while y.numerator == 0:
y = self.init_num(max_num)
self.nums.append(y)
def str_num(self, num):
'''字符串化一个分数'''
inter = int(num.numerator / num.denominator)
numerator = int(num.numerator % num.denominator)
if numerator == 0:
'''如果为空'''
str_num = str(inter)
else:
str_num = str(num.numerator) + '/' + str(num.denominator)
return str_num
def init_expression(self):
'''生成一个算术表达式的字符串形式'''
self.str = ''
i = 0
self.exp = []
again = False
for x in self.operators:
if again:
self.str += x + ' '
elif x == '(':
self.str += x + ' '
elif x == ')':
self.str += self.str_num(self.nums[i]) + ' '
i += 1
self.str += x + ' '
again = True
else:
self.str += self.str_num(self.nums[i]) + ' '
self.str += x + ' '
i += 1
self.str += self.str_num(self.nums[-1]) + ' ='
Calculate类
def getResult(expression):
'''储存结果'''
stackValue = []
for item in expression:
if item in ["+", "-", "×", "÷"]:
n2 = stackValue.pop()
n1 = stackValue.pop()
result = cal(n1, n2, item)
if result < 0:
return -1
stackValue.append(result)
else:
stackValue.append(item)
return stackValue[0]
def cal(n1, n2, op):
'''计算结果'''
if op == "+":
return n1 + n2
if op == "-":
return n1 - n2
if op == "×":
return n1 * n2
if op == "÷":
return n1 / n2
六、测试运行
运行结果如下所示:
六、效能分析
本代码效能分析使用了python内部profile库:纯Python实现的性能测试模块
实现:
其中:
ncall:函数运行次数
tottime: 函数的总的运行时间,减去函数中调用子函数的运行时间
第一个percall:percall = tottime / nclall
cumtime:函数及其所有子函数调整的运行时间,也就是函数开始调用到结束的时间。
第二个percall:percall = cumtime / nclall
效率不高的原因:
u 一条参数中使用很多方法嵌套。如:main()函数中:
real_res = Calculate.getResult(inf.str_to_fraction(inf.to_suffix_expression(ari.str)))
u 调用类中的其他函数。