0.作业基本信息
这个作业属于哪个课程 | 班级的链接 |
---|---|
这个作业要求在哪里 | 作业要求的链接 |
这个作业的目标 | 实现一个自动生成小学四则运算题目的命令行程序 |
成员 | 黄华健-3121005294,黄兆康-3121005171 |
其他参考文献 | … |
代码仓库地址 |
1.PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 60 |
· Estimate | · 估计这个任务需要多少时间 | 300 | 380 |
Development | 开发 | 200 | 300 |
· Analysis | · 需求分析 (包括学习新技术) | 200 | 200 |
· Design Spec | · 生成设计文档 | 30 | 50 |
· Design Review | · 设计复审 | 30 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
· Design | · 具体设计 | 20 | 30 |
· Coding | · 具体编码 | 300 | 300 |
· Code Review | · 代码复审 | 30 | 40 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 100 |
Reporting | 报告 | 100 | 100 |
· Test Repor | · 测试报告 | 50 | 50 |
· Size Measurement | · 计算工作量 | 20 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 20 |
· 合计 | 1430 | 1690 |
2.效能分析
用pycharm的Profile插件对程序进行性能分析
程序中消耗最大的函数是to_infix()函数
def to_show_infix(self, expr):
expression = Functions.get_show_hou(self, expr)
stack = []
for token in expression:
if token in Functions.operators:
if len(stack) >= 2:
operand2 = stack.pop()
operand1 = stack.pop()
# 添加括号,保证运算顺序
stack.append(f"({operand1} {token} {operand2})")
else:
operand = stack.pop()
if len(stack) > 0 and isinstance(stack[-1], list):
# 将操作数添加到前一个表达式中
stack[-1].append(token)
stack[-1].append(operand)
elif len(stack) > 0:
# 两个操作数和一个运算符组成一个表达式
stack.append([stack.pop(), token, operand])
else:
# 第一个操作数
stack.append(operand)
else:
# 操作数入栈
stack.append(token)
if len(stack) == 1 and isinstance(stack[0], list):
# 如果只有一个表达式,则返回这个表达式
stack = stack[0]
return stack
3.设计与实现过程
3.1设计原理
为实现小学四则运算题目出题程序,我们用随机生成后缀表达式,判断是否重复后,转换为中缀表达式,计算结果,并输出题目和答案至文档,统计用户的作答情况。
3.2实现过程
1.随机生成后缀表达式
通过入栈出栈操作组成算式,生成过程中确保除数不为0,确保相邻的操作数之间有运算符,确保最后一个元素不为操作符,使生成的表达式合法,并确保式子结果不为负数。
2.判断后缀表达式是否重复
每生成一个后缀表达式,就将生成的后缀表达式存入列表,下次生成表达式就先判断列表中是否已存在,存在即重复,重新生成后缀表达式。
3.将后缀表达式转换为中缀表达式
通过栈类型来实现。
4.输出题目和答案至文档
为实现分数运算,将所有数字转化为fractions.Fraction类型,将假分数转化为带分数,跟运算符结合成中缀表达式,写入题目文档,计算答案,写入答案文档。
5.读取题目及作答文档并导出作答情况
读取题目文件内容,将数字读入存为fractions.Fraction类型,将带分数转化为整数加分数,跟运算符结合成中缀表达式,计算结果,与作答结果比较,导出作答情况
一个functions类中主要有5个函数
分别为gen_writ(),get_ans(),generate_expression(),to_show_infix(),gen_fraction()
关键函数为to_show_infix(expression)函数
流程图如下
4.代码说明
下面是三个重要的函数的
# 生成随机的后缀表达式
def generate_expression(self, max_num):
stack = []
# 随机生成表达式的长度
for i in range(random.randint(3, 10)):
if len(stack) >= 2:
# 随机选择两种操作:加入一个操作符或者进行一次运算
if random.randint(0, 1) == 1:
# 随机选择一个运算符
operator = Functions.random_operator(self)
operand2 = stack.pop()
operand1 = stack.pop()
# 确保除数不为0
if operator == '÷' and operand2 == '0':
operand2 = Functions.gen_number(self, max_num)
stack += [operand1, operand2, operator]
else:
# 添加一个操作数
# 确保相邻的操作数之间有运算符
if isinstance(stack[-1], str):
stack.append(Functions.random_operator(self))
stack.append(Functions.gen_number(self, max_num))
else:
# 第一个操作数
stack.append(Functions.gen_number(self, max_num))
# 确保最后一个元素不为操作符
if isinstance(stack[-1], str):
stack.pop()
return stack
# 将后缀表达式转换为中缀表达式
def to_show_infix(self, expr):
expression = Functions.get_show_hou(self, expr)
stack = []
for token in expression:
if token in Functions.operators:
if len(stack) >= 2:
operand2 = stack.pop()
operand1 = stack.pop()
# 添加括号,保证运算顺序
stack.append(f"({operand1} {token} {operand2})")
else:
operand = stack.pop()
if len(stack) > 0 and isinstance(stack[-1], list):
# 将操作数添加到前一个表达式中
stack[-1].append(token)
stack[-1].append(operand)
elif len(stack) > 0:
# 两个操作数和一个运算符组成一个表达式
stack.append([stack.pop(), token, operand])
else:
# 第一个操作数
stack.append(operand)
else:
# 操作数入栈
stack.append(token)
if len(stack) == 1 and isinstance(stack[0], list):
# 如果只有一个表达式,则返回这个表达式
stack = stack[0]
return stack
# 计算运算结果
def get_ans(self, expr, note=1):
frac = "Fraction("
if note == 1: # 生成表达式时使用
infix_expression = Functions.to_show_infix(self, expr) # 后缀转中缀
expression = infix_expression[0]
else: # 读取表达式时使用
expression = expr
# print("中缀表达式:", infix_expression)
formul1 = Functions.tranc(self, expression) # x换成*
# print(formul1)
formul1 = Functions.first(self, formul1) # 分数的/换,
formul2 = ''
flag = 0
length = len(formul1)
for i in range(length): # 将带分数的‘换为+
if formul1[i] == "'":
formul2 += '+'
continue
if formul1[i] not in Functions.numb and formul1[i] != "'":
formul2 += formul1[i]
continue
if formul1[i] in Functions.numb and formul1[i - 1] not in Functions.numb: # 读取到的数拼接成分数函数,并看情况加括号
formul2 = formul2 + '(' + frac + formul1[i]
if formul1[i + 1] not in Functions.numb and formul1[i + 1] != "'":
formul2 = formul2 + ')' + ')'
elif formul1[i + 1] not in Functions.numb and formul1[i + 1] == "'":
formul2 += ')'
flag = 1
continue
elif formul1[i] in Functions.numb:
formul2 = formul2 + formul1[i]
if formul1[i + 1] not in Functions.numb and formul1[i + 1] != "'":
if flag == 1:
formul2 = formul2 + ')' + ')' + ')'
flag = 0
else:
formul2 = formul2 + ')' + ')'
elif formul1[i + 1] not in Functions.numb and formul1[i + 1] == "'":
formul2 = formul2 + ')'
flag = 1
continue
formul2 = Functions.tranz(self, formul2) # ÷换成/
# print(formul2)
ans = eval(formul2)
return ans
5.测试运行
6.单元测试
测试代码
import unittest
import random
import math
from fractions import Fraction
from functions import *
class MyTestCase(unittest.TestCase):
def test_something(self):
function = Functions()
exerc = open('Exercises.txt', 'w').close()
exerc = open('Exercises.txt', 'w')
ans = open('Answers.txt', 'w').close()
ans = open('Answers.txt', 'w')
s = ['1', '2', '3', '+', '6', '×', '+', '3', '4', '*', '-']
formul = "(1+((2+3)×6)-3×4)"
self.assertEqual(function.get_ans(formul, 0), 19)
self.assertEqual(function.gen_writ(exerc, ans, 2, 10), 0)
self.assertEqual(function.check(), 0)
if __name__ == '__main__':
unittest.main()
代码覆盖率
7.项目小结
1.结对编程需要考虑代码的可读性。团队协作当中,代码需要成员之间相互理解,制定一些标准,在必要的地方给出合适的注释是非常有必要的。
2.要合理分配工作,发挥团队效益。结对编程需要有合理的分工,发挥团队作用,提高彼此之间的沟通能力和团队协作能力,这些是个人编程所欠缺的。
3.兆康的资料搜索能力较强,但是python语言需要再系统学习一下。华健算法的设计思维较好,但是编码能力和成员沟通能力还需强化。