结对作业
这个作业属于哪个课程 | 信安1912-软件工程 |
---|---|
这个作业要求在哪里 | 结对项目 |
这个作业的目标 | 实现一个自动生成小学四则运算题目的命令行程序(也可以用图像界面,具有相似功能) |
正文
GitHub链接:姜珺杨 3219005446 程雨秋 3219005444
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 5 | 5 |
· Estimate | · 估计这个任务需要多少时间 | 5 | 5 |
Development | 开发 | 640 | 725 |
· Analysis | · 需求分析 (包括学习新技术) | 120 | 120 |
· Design Spec | · 生成设计文档 | 10 | 15 |
· Design Review | · 设计复审 | 15 | 15 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 15 | 5 |
· Design | · 具体设计 | 60 | 60 |
· Coding | · 具体编码 | 240 | 300 |
· Code Review | · 代码复审 | 60 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 150 |
Reporting | 报告 | 90 | 145 |
· Test Repor | · 测试报告 | 30 | 50 |
· Size Measurement | · 计算工作量 | 5 | 5 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 90 |
· 合计 | 735 | 875 |
实现过程
需求分析
- 实现一个自动生成小学四则运算题目的命令行程序(也可以用图像界面,具有相似功能)。
- 能够控制生成参数,如生成题目数量和数字范围(自然数、真分数和真分数分母)。
- 结果不能出现复数,分数表达结果应该为真分数和带分数。
- 控制运算符数量。
- 指定文件的读写。
- 对用户答案进行判定并打分。
实现思路
第一版本想采用普通的方式生成,但无法保证不重复事后查重也不好整。
第二版本想采用eval+re处理计算结果大失败。
第三版本和第四版本是现在的版本。
相关技术
- 二叉树
先将表达式转换为二叉树,然后通过先序遍历二叉树的方式求出表达式的值。
原理:我们把表达式 3+2*9-16/4 转换成二叉树的表现形式。
特点:操作数字都是叶子结点 运算符都是内部结点 优先运算符都在下面
- easygui
个人非常喜欢,因为格外好用,推荐大家都来用
EasyGUI与其他GUI生成器的不同之处在于EasyGUI不是事件驱动的。相反,所有GUI交互都是通过简单的函数调用来调用的,本身是对python自带的Tkinter的封装。 - 面向对象的开发
虽然我觉得我用的不太好但还是提一下毕竟第一次尝试在python里面向对象。
面向对象编程中,将函数和变量进一步封装成类,类才是程序的基本元素,它将数据和操作紧密地连结在一起,并保护数据不会被外界的函数意外地改变。类和和类的实例(也称对象)是面向对象的核心概念,是和面向过程编程、函数式编程的根本区别。
代码说明
class Question
- def init(self,number_range,count,probability): 调用,传参
Python中“init”的意义是在类实例创建的时候自动会被执行的。这里只是自己重定义了__init__的行为。 - fraction_char(self,figure_array):
- def random_fraction(self):
- def new_num(self) :
- def operator_char(self,operator) :
- def new_random(self,range): 随机数
- def Calculation(self,figure1,figure2,operate):
#普通的手算,因为百度查了说eval有很大bug根本不建议用(事实上也确实整出bug了)
if operate == 1:
numerator = figure1[0]*figure2[1]+figure2[0]*figure1[1]
denominator = figure1[1]*figure2[1]
elif operate == 2:
numerator = figure1[0]*figure2[1]-figure2[0]*figure1[1]
denominator = figure1[1]*figure2[1]
if numerator < 0:
return [numerator,denominator]
elif operate == 3:
numerator = figure1[0]*figure2[0]
denominator = figure1[1]*figure2[1]
elif operate == 4 :
numerator = figure1[0]*figure2[1]
denominator = figure1[1]*figure2[0]
result = self.Simple_fraction(numerator,denominator)
return result
- def Simple_fraction(self,numerator,denominator):
- def Common_factor(self,numerator,denominator): 辗转相除获得最大公约数
- def new_exp(self,count):
#现学现卖的可怜的树
if count == 1:
figure = self.new_num()
return{
'expression_array': figure['figure'],
'expression': figure['figure_char'],
'answer': figure['figure_array']
}
else :
leftcount = self.new_random(count -1)
rightcount = count - leftcount
left = self.new_exp(leftcount)
right = self.new_exp(rightcount)
operate = self.new_random(4)
if operate == 4 and right['answer'][0] == 0:
t = left
left = right
right = t
answer = self.Calculation(left['answer'],right['answer'],operate)
if answer[0] < 0:
t = left
left = right
right = t
answer = self.Calculation(left['answer'],right['answer'],operate)
leftvalue = left['answer'][0]/left['answer'][1]
rightvalue = right['answer'][0]/right['answer'][1]
expression_array = [left['expression_array'],operate,right['expression_array']]
if type(left['expression_array'])!=list and type(right['expression_array'])!=list: #两个子树都为值
if (operate == 1 or operate == 3) and leftvalue < rightvalue:
expression_array = [right['expression_array'],operate,left['expression_array']]
elif type(left['expression_array'])==list and type(right['expression_array'])==list:# 两个子树都为树
if operate == 1 or operate == 3:
if leftvalue == rightvalue and left['expression_array'][1] < right['expression_array'][1]:#树的值相等时,运算符优先级高的在左边
expression_array = [right['expression_array'],operate,left['expression_array']]
elif leftvalue < rightvalue :
expression_array = [right['expression_array'],operate,left['expression_array']]
if operate in [3,4] :
if left['expression_array'][1] in [1,2]:
left['expression'] = '(' + left['expression'] + ')'
if right['expression_array'][1] in [1,2]:
right['expression'] = '(' + right['expression'] + ')'
else:#一边的子树为树
if operate == 1 or operate == 3:
if type(right['expression_array']) == list:
expression_array = [right['expression_array'],operate,left['expression_array']]
if operate in [3,4] :
if type(left['expression_array']) == list and left['expression_array'][1] in [1,2] :
left['expression'] = '(' + left['expression'] + ')'
if type(right['expression_array']) == list and right['expression_array'][1] in[1,2]:
right['expression'] = '(' + right['expression'] + ')'
expression = left['expression']+' '+self.operator_char(operate)+' '+right['expression']
return {
'expression_array': expression_array,
'expression': expression,
'answer': answer
}
class Parameter
- def init(self,ret):
- def gui(): 图形界面
- def get_parameter(self,ret): 传参
- def getexpression_list(self, expression_count, number_range, count, probability) : 传参,生成式子list
- def check(self,expression,List): 查重,其实还有提升空间
- def write_file(self,expressionlist): 写入文件
- def check_answers(path1,path2):
#批改时的调用
list = Parameter.get_answers(path1)
grade = Parameter.checks(list,path2)
Parameter.write_grade(grade)
- def write_grade(grade):
- def get_answers(path2):
- def checks(list,path1):
#评分
with open(path1,"r",encoding="gbk") as f:
lines = f.readlines()
listA =[]
correct=[]
wrong=[]
grade=[]
for line in lines:
right_answer = line.split('.')[1]
right_answer1 = right_answer.replace(' ','')
listA.append(right_answer1)
i = 0
if (i) < len(listA):
for char in listA:
if char != list[i]:
wrong.append(i+1)
i+=1
elif char == list[i]:
correct.append(i+1)
i+=1
grade.append(correct)
grade.append(wrong)
return grade
测试运行
运行界面
运行结果
一万道生成:
批改题目:
批改对传入文件的格式要求很严格,但这个确实不太好解决暂时就先这样吧。
效能分析
覆盖率:
覆盖率不是百分百的原因是我在测试时采用了比较简单的测试方式,导致异常处理和部分条件下的退出情况没有用到,根据生成文件可以看出代码覆盖率没有问题。
cProfile
改进过程
说实话看到结果时图形界面居然占了这么大的运行比例其实超出了我们的预料,毕竟以前也没对带图形界面的程序执行过这些,其次可以看出在生成式子的时候也占用了大量的时间。
事实上在改进的过程中我们也挺困惑的,上文也说到中途其实换过了实现方法从最开始普通的用字符串后缀表达式正则表达式等等各种方法都学了的换成了现在的采用二叉树的技术,虽然可能是我个人水平有限结果这次又进入了一边写一边学的状态,就最终参考了很多资料,理论上来说目前的这个版本已经性能挺满意的了,cprofile测试的时候生成了500道的题目能有这样的运行效率说实话我们非常满意。
项目小结
姜珺杨
这是我第一次和人合作写代码,倒也不是没参与过小组合作但主要那种是分下来任务然后各写各的,就说实话还是单人的那种小组合作,对我来说是很新奇的一种体验,在这次的作业中,可以说我深刻感受到了自己水平不足以及大二的数据结构以及忘得差不多了的这种事情,当然因此我又学到了很多东西,毕竟写代码的事情嘛本来就是边学习边写边进步的过程,而这次我不仅仅在学习方面进步了,也在与人合作,与人沟通的方面迈出了新的一步,可以说合作的过程非常愉快非常有意义,总体来说也非常顺利,虽然说写的过程中因为我个人能力有限中间还换过思路写的有点痛苦但好在结果还是成功完成了作业!在此感谢我的队友完全没有嫌弃我的水平虽然开头还在担心自己找不找得到人一起组队但最后能够两个人一起完成作业真的太好了哈哈哈哈哈哈哈!
程雨秋
在这次的结对项目中,我第一次接触到了代码审计的工作,可以说我们两个人都是新手司机上路,但是感谢亲爱的队友总之沟通的很顺利,在完成项目的过程中,我体会到了合作的重要性,如果没有成员之间的意见交换与集思广益,我很难成功的完成这个项目,也很难有这么大的收获和这一次全新的二让人受益匪浅的经历,结对编程的目标是为了达到'1+1>2'的效果,我们两个人虽然是初次合作,难以达到理想的效果,但我觉得至少达到了'1+1=2'水平,也很值得庆祝,在此感谢亲爱的队友顺利两个人一起完成了这次项目。