软工作业3:结对项目-四则运算
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/CSGrade21-12 |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/CSGrade21-12/homework/13016 |
这个作业的目标 | 与队友合作完成项目,实现四则运算算式生成 |
团队成员:
沈俊杰 | 3121004710 | https://github.com/13ugYellow/13ugYellow/tree/master/结对项目 |
---|---|---|
梁志聪 | 3121004704 |
PSP表格:
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
Estimate | 估计这个任务需要多少时间 | 1030 | 1220 |
Development | 开发 | 480 | 600 |
Analysis | 需求分析 (包括学习新技术) | 120 | 150 |
Design Spec | 生成设计文档 | 30 | 40 |
Design Review | 设计复审 | 30 | 30 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
Design | 具体设计 | 60 | 40 |
Coding | 具体编码 | 120 | 150 |
Code Review | 代码复审 | 20 | 10 |
Test | 测试(自我测试,修改代码,提交修改) | 30 | 30 |
Reporting | 报告 | 30 | 50 |
Test Report | 测试报告 | 30 | 40 |
Size Measurement | 计算工作量 | 10 | 10 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 20 | 20 |
合计 | 1030 | 1220 |
能效分析:
生成四则运算算式的总时间:
答案对比的总时间:
生成四则运算算式的时间占比:
可以看到在creat函数中:
将逆波兰式生成规范二叉树时间占比较大,但这是为了判重的必须步骤,暂时无法优化。
答案对比的时间占比:
其中grade函数占比最大,看在grade函数中:
发现是读取文件的时间占比较大,故无法优化。
设计实现过程:
总体思想:
随机生成中缀表达式——>生成逆波兰表达式——>转化为规范二叉树——>计算结果,结果为负数则舍弃二叉树——>表达式判重——>写入文件
类与函数:
- class Arith(object)
- creat(self, problem_number, r) # 生成四则运算和答案在判重后写入文件
- main(self, arith, argv) # 支持命令行输入参数
- class BinaryTree(object) # 二叉树
- out_put_tree(self, tree, s) # 返回二叉树逆波兰形式的字符串
- class Caculation(object)
- caulate(self, op, f1, f2) # 计算f1 op f2 的值(op为运算符)
- max(self, num1, num2) # 比较两分数大小
- class Check(object) # 判重
- check_tree(self, tree) # 对二叉树进行判重,若不重复返回True
- class Compare(object)
- grade(self, exercise_file, answer_file) # 比较两文件答案并记录
- class Create(object) # 生成四则运算表达式
- create_operator(self) # 随机生成运算符
- create_arith(self, r) # 生成范围在r以内的四则运算表达式
- proper_fraction(self, list) # 将假分数化为带分数
- class Fractions(object) # 分数类
- class CreateTree(object) # 生成规范二叉树
- toRPN(self, list) # 生成逆波兰表达式
- createTree(self, suffix) # 将逆波兰式转化为规范化二叉树
- priority(self, operatorout, operatorin) # 判定优先级
代码说明:
生成四则运算表达式
def create_arith(self, r):
x = 0
list = []
# 随机生成运算符的数量
operator_num = random.randint(1, 3)
e1 = Create()
e2 = Create()
if operator_num == 1:
list.append(e1.create_number(r))
list.append(e2.create_operator())
list.append(e1.create_number(r))
elif operator_num == 2:
start = random.randint(0, 2)
end = 0
if start > 0:
end = start + 1
for i in range(1, 4):
if i == start:
list.append("(")
list.append(e1.create_number(r))
if i == end:
list.append(")")
list.append(e2.create_operator())
list.pop()
elif operator_num == 3:
start = random.randint(0, 3)
end = 0
if start > 0:
end = start + 1 + random.randint(0, 1)
if end >= 4:
end = 4
for i in range(1, 5):
if i == start:
list.append("(")
list.append(e1.create_number(r))
if i == end:
list.append(")")
list.append(e2.create_operator())
list.pop()
else:
list.append(e1.create_number(r))
list.append(e2.create_operator())
list.append(e1.create_number(r))
return list
生成逆波兰式
def toRPN(self, list):
right = []
aStack = []
position = 0
while True:
if self.isOperator(list[position]):
if list == [] or list[position] == "(":
aStack.append(list[position])
else:
if list[position] == ")":
while True:
if aStack != [] and aStack[-1] != "(":
operator = aStack.pop()
right.append(operator)
else:
if aStack != []:
aStack.pop()
break
else:
while True:
if aStack != [] and self.priority(list[position], aStack[-1]):
operator = aStack.pop()
if operator != "(":
right.append(operator)
else:
break
aStack.append(list[position])
else:
right.append(list[position])
position = position + 1
if position >= len(list):
break
while aStack != []:
operator = aStack.pop()
if operator != "(":
right.append(operator)
return right
将逆波兰式转换成二叉树并规范化
def createTree(self, suffix):
stacks = []
for i in range(0, len(suffix)):
tree = BinaryTree()
ob = suffix[i]
c = Caculation()
if self.isOperator(ob):
t2 = BinaryTree()
t1 = BinaryTree()
t2 = stacks.pop()
t1 = stacks.pop()
if ob == '-' and t1.value <= t2.value:
return None
else:
if self.maxTree(t1, t2):
tree.set_date(ob)
tree.set_left(t1)
tree.set_right(t2)
tree.set_value(c.caulate(ob, t1.value, t2.value))
else:
tree.set_date(ob)
tree.set_left(t2)
tree.set_right(t1)
tree.set_value(c.caulate(ob, t1.value, t2.value))
stacks.append(tree)
else:
tree.set_value(ob)
tree.set_date(ob)
stacks.append(tree)
return tree
对二叉树进行判重
def check_tree(self, tree):
if self.check == []:
self.check.append(tree)
return True
else:
for i in range(len(self.check)):
if self.check[i] == tree:
return False
self.check.append(tree)
return True
计算相应运算符下两参数的值
def caulate(self, op, f1, f2):
result = Fractions()
n1 = int(f1.numerator)
d1 = int(f1.denominator)
n2 = int(f2.numerator)
d2 = int(f2.denominator)
list = []
if op == '+':
re = Fraction(n1, d1) + Fraction(n2, d2)
elif op == '-':
re = Fraction(n1, d1) - Fraction(n2, d2)
elif op == '×':
re = Fraction(n1, d1) * Fraction(n2, d2)
else:
re = Fraction(n1, d1) / Fraction(n2, d2)
return re
对两个文件中的答案进行比较并记录
def grade(self, reanswer_file, answer_file):
correct = []
wrong = []
co = 0
wr = 0
with open(answer_file, 'r', encoding='utf-8') as f1, open(reanswer_file, 'r', encoding='utf-8') as f2:
answers = f2.readlines()
line = 0
for r_answers in f1.readlines():
if answers[line] == r_answers:
co += 1
correct.append(line+1)
else:
wr += 1
wrong.append(line+1)
line += 1
with open('Grade.txt', 'w') as f3:
f3.write(f"Correct: {str(co)} ({', '.join(str(s) for s in correct if s not in [None])})" + '\n')
f3.write(f"Wrong: {str(wr)} ({', '.join(str(s) for s in wrong if s not in [None])})" + '\n')
print("文件比较完成")
生成问题和答案在判重后写入文件
def creat(self, problem_number, r):
creat_pro = Create()
t = BinaryTree()
c = Check()
with open("Exercises.txt", "w", encoding='utf-8') as file1, open("Answer.txt", "w", encoding='utf-8') as file2:
num = 0
while num < problem_number:
arith = creat_pro.create_arith(r) # 生成四则运算列表
Ju = Judge()
al = Ju.toRPN(arith) # 将列表转换成逆波兰式
string = creat_pro.to_string(creat_pro.proper_fraction(arith))
ta = Ju.createTree(al) # 将逆波兰式生成规范二叉树
if ta:
val = str(creat_pro.pop_fracte(ta.value))
if c.check_tree(t.to_string(ta)): # 进行判重
file1.write("%d. " % (num+1) + string + '\n')
file2.write("%d. " % (num+1) + val + '\n')
num +=1
print("四则运算题目生成完毕,数量为%d个" % problem_number)
支持命令行键入参数
# @profile()
def main(self, arith, argv):
problem_number = None
num_range = None
exercise_file = None
answer_file = None
try:
opts, args = getopt.getopt(argv, "n:r:e:a:")
except getopt.GetoptError:
print('Error: main.py -n <problem_number> -r <num_range>')
print(' or: main.py -e <ReAnswer_file>.txt -a <Answer_file>.txt')
sys.exit(2)
for opt, arg in opts:
if opt in("-n"):
problem_number = int(arg)
elif opt in ("-r"):
num_range = int(arg)
elif opt in("-e"):
exercise_file = arg
elif opt in("-a"):
answer_file = arg
if problem_number and num_range:
arith.creat(problem_number, num_range)
elif exercise_file and answer_file:
compare = Compare()
compare.grade('ReAnswer.txt', 'Answer.txt')
else:
print('Error: main.py -n <problem_number> -r <num_range>')
print(' or: main.py -e <ReAnswer_file>.txt -a <Answer_file>.txt')
测试运行:
命令行参数不完整测试:
生成10道题目测试:
对比答案测试:
项目小结:
本次结对项目中,我们二人得益于住在同一个宿舍,有更多的交流时间与分工合作。我们二人的代码能力尚不成熟,在完成项目的过程中编码环节对我们来说是艰难的,只能通过对网上的算法的借鉴和向同学提问学习来完成代码的编写。我们在这次项目中,发现了相比于编写代码,对于团队项目来说更重要的是理清项目的总体流程,这样才能够便于我们分工合作。代码的模块化便可以极大的辅助我们的合作,这次结对项目也使得我们二人的代码风格更加规范,不再是写出一大堆只为了完成一个目的的代码。总体来说,结对项目让我们可以吸收他人的想法,更加开阔自己对于实现项目的视野,使得思路更加清晰,然而这也让我们知道了我们贫瘠的代码水平限制了我们的思想,我们对彼此的建议都认为提升代码底力是我们的当务之急,也希望可以有更多机会可以结对合作相互督促进步。