高级软件工程第二次作业(四则运算生成器)
1.项目github代码
四则运算生成器github代码
2.PSP时间预估
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 15 | 15 |
· Estimate | · 估计这个任务需要多少时间 | 15 | 15 |
Development | 开发 | 465 | 612 |
· Analysis | · 需求分析 (包括学习新技术) | 10 | 6 |
· Design Spec | · 生成设计文档 | 20 | 25 |
· Design Review | · 设计复审 (和同事审核设计文档) | 10 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 8 |
· Design | · 具体设计 | 25 | 23 |
· Coding | · 具体编码 | 4x60 | 6x60 |
· Code Review | · 代码复审 | 100 | 120 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 60 |
Reporting | 报告 | 120 | 150 |
· Test Report | · 测试报告 | 30 | 30 |
· Size Measurement | · 计算工作量 | 10 | 10 |
·Postmortem&Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 80 | 60 |
合计 | 565 | 762 |
3.需求分析
- 随机生成100以内的整数
- 随机生成真分数
- 随机生成1~10个运算符(附加)
- 使用 -n 参数控制生成题目的个数
- 能够处理用户的输入,并对每道题的用户输入进行对错的判断
- 进行正确率的统计以及分数评定
4.解题思路
4.1按照需求分析中提到的要点查找相关资料
(1)在python中有生成随机数的函数,需要引入包random,方法random.randint(a,b)即可随机生成a,b间的整数。
(2)查阅python文档及相关资料,查找支持真分数表示的方法。在包fractions中,方法Fraction(x,y)可实例化分数x/y。该方法也支持python中的算术运算。
(3)随机生成运算符:
定义列表保存四则运算符:op = [“+”,“-”,“×”, “/”],使用random中的choice(op)方法随机生成运算符,返回的是运算符字符。
如何将该字符与运算符进行对应?
定义一个字典op_dict= {"+":"+", "-":"-", "×":"*", "÷":"/"},可将随机运算符对应到python中的运算符中:op_dict[random.choice(op)]
(4)尝试写只生成一个四则运算算式的生成器(生成3个单运算符,数字随机,运算符随机)。
在计算式子的结果时,需要将中缀式转换成后缀式以及对后缀式计算结果计算时,都需要栈来实现。因此需要定义一个栈Stack类。
(5)实现使用命令行参数控制生成题目的个数。
查阅相关文档[1]文档[2],学习如何使用命令行参数。
通过命令行参数获取需要生成的题目个数,再经过循环,生成相应数量的题目,并且在每次循环中对用户的输入进行结果的判断,对答对题目的个数进行计算。循环结束后进行分数的输出。
4.2设计实现过程
此次项目的代码包括了两个文件:arithmetic.py文件以及start.py文件。以便于进行管理以及功能的改善。
arithmetic.py文件中包含:
(1)Stack()类:实现栈的功能,便于中缀式转化成后缀式,以及后缀式结果的计算。
(2)ran_num(x_num)方法:用于随机生成x_num个数(整数或分数),返回值为随机数列表,例如[1,2,3,4]。
(3)ran_oper(x_op)方法:用于随机生成x_op个运算符,返回值为运算符列表,例如[“+”,“-”,“×”, “/”]。
(4)combin_num_opera(x_op)方法:实现将随机数列表和运算符列表进行交叉合并,返回交叉合并后的列表,例如:[1,”+”,2,”*”,3,”-”,4]
(5)operate(lst)方法:接收合并后的列表作为参数,对列表内容进行计算。该方法中包含了将中缀式形式的列表利用栈Stack()转化为后缀式的列表。再对后缀式的列表利用栈stack()进行最终结果的计算,该方法返回值即为算式的正确结果。
(6)display_arith(lst)方法:实现将随机数与随机操作符合并列表转化成四则运算式的字符串形式,便于输出显示。该方法返回值为四则运算式字符串。
start.py文件中包含:
main(argv)主函数:argv参数来自命令行参数,其将通过循环控制题目个数的生成。
4.3 代码说明
arithmetic.py文件:
(1)stack类,用于逆波兰式的转化以及运算式的计算
class Stack: def __init__(self): self.items = [] def isEmpty(self): return self.size() == 0 def push(self, item): self.items.append(item) def pop(self): return self.items.pop() def peek(self): if not self.isEmpty(): return self.items[len(self.items)-1] def size(self): return len(self.items)
(2)ran_num(x_num)方法用于生成x_num个随机数,该方法返回随机数列表。使用flag标记,取随机数0或1,当flag为0时随机生成一个整数;当flag为1时随机生成一个分数。
def ran_num(x_num):#num_list列表保存随机生成的数值num_list = [] for i in range(0,x_num): #设置flag标志,随机生成0或1,当flag为0时,生成一个随机整数;当flag为1时,生成一个随机分数flag = randint(0,1)if(flag == 0):num = randint(0,10)num_list.append(num)else:tmp1, tmp2= randint(1,10), randint(1,10)if(tmp1 > tmp2):frac = Fraction(tmp2, tmp1)else:frac = Fraction(tmp1, tmp2)num_list.append(frac)return num_list
(3)ran_oper(x_op)方法用于生成x_op个随机运算符符,该方法返回随机运算符列表。
def ran_oper(x_op):#设置列表op保存四种操作符#通过取随机数(范围为0~3),来确定所取操作符在列表中的下标,以实现随机生成操作符op = ["+", "-", "*", "/"] op_list = []for i in range(0,x_op):tmp = choice(op)op_list.append(tmp)return op_list
(4)combin_num_opera(x_op)方法将x_op+1个随机数与x_op个随机运算符列表进行交叉合并
def combin_num_opera(x_op):num = ran_num(x_op + 1)oper = ran_oper(x_op)arith_list = list(itertools.chain.from_iterable(zip(num,oper)))length = len(num)arith_list.append(num[length-1])return arith_list
(5)operate(lst)方法用于对生成的四则运算式进行结果的计算,返回值为计算结果
def operate(lst):#设置运算符优先级op_oprior = {"+":0, "-":0, "*":1, "/":1}op = ["+", "-", "*", "/"]#postfix_lst保存后缀表达式postfix_lst = []#利用栈实现中缀表达式与后缀表达式的转换s = Stack()for tmp in lst:if(tmp not in op):postfix_lst.append(tmp)else:while(s.size() > 0 and op_oprior[tmp] <= op_oprior[s.peek()]):postfix_lst.append(s.pop())s.push(tmp)len = s.size()while(len > 0):postfix_lst.append(s.pop())len = len -1
# print postfix_lst#利用栈计算后缀表达式的值resault = Stack()for item in postfix_lst:if(item not in op):resault.push(item)else:b = resault.pop()a = resault.pop()if(item == "+"):resault.push(a+b)if(item == "-"):resault.push(a-b)if(item == "*"):resault.push(a*b) #在进行除法运算时注意判断两个数是否都为整数#若都为整数,需要将除法直接表示为Fraction(a,b),否则计算结果将只取整数部分 if(item == "/"):if(isinstance(a, int) and isinstance(b, int)):resault.push(Fraction(a,b))else:resault.push(a/b)return resault.peek()
(6)display_arith(lst)方法实现将随机数与随机操作符合并列表,转化成字符串形式,便于算式的输出显示
def display_arith(lst):#利用字典将python中的运算符与数学运算符进行对应op_dict = {"+":"+", "-":"-", "*":"×", "/":"÷"} op = ["+", "-", "*", "/"]length = len(lst)for i in range(length):if(lst[i] in op):lst[i] = op_dict[lst[i]]#将列表中每个元素转化成字符再进行连接形成字符串return ''.join(str(v) for v in lst)
start.py文件:
(1)main(argv)函数从命令行获取参数argv决定生成多少个四则运算题,并对每次用户输入结果进行判断,最终输出分数。
def main(argv):#命令行参数:当输入-h时显示帮助文档;当输入-n时,继续输入的数值即为题目个数number = 0try:options,args = getopt.getopt(sys.argv[1:],"hn:",["help","num="])except getopt.GetoptError:print ('arithmetic.py -n <the number of questions> -h <help for this program>')sys.exit(2)for opt,arg in options:#当输入为-h时,打印提示信息if opt == '-h':print ('使用方法:arithmetic.py -n <the number of questions> or -h <help for this program>')sys.exit()#当输入为-n时,后续输入的数字即为题目个数elif opt in ("-n", "--num"):number = int(arg)print (u"本次共"+str(number)+u"题,满分100分\n")#count用来计数回答正确的题目个数count = 0for i in range(1,number+1):#op_num控制随机生成的运算符个数op_num = randint(1,10)x = arithmetic.combin_num_opera(op_num)try:ans = arithmetic.operate(x)except Exception as e:print("Error:",e)# print ans# pdb.set_trace()print (str(i)+": "+arithmetic.display_arith(x) + "= ",end="")#由于要考虑输入的可能是一个分数#因此直接以字符串类型输入,再将正确结果转化为字符串进行比较res = input()if(res == str(ans)):print (u"正确!"+"\n")count = count + 1else:print (u"不正确!正确答案是:"+str(ans)+"\n")#最终成绩保留小数点后两位score = round(100 * float(count) / float(number),2)print (u"本次练习共答对"+str(count)+"题,"+u"总得分: " + str(score))
5.测试运行
运行结果截图:
测试结果截图:
测试错误1:
出现错误,在仔细校对arithmic.py代码与测试代码时发现,在编写测试代码检测交叉合并列表函数是否能够使操作数与运算符交叉合并时,错把列表元素当做下标进行了处理,错误代码见下:
arith_lst = arithmetic.combin_num_opera(n)
for x in arith_lst:if(x % 2 == 0):count_num = count_num + 1if(isinstance(x,int) or x<1):flag_num = 1else:flag_num = 0else:count_op = count_op + 1if(x in op):flag_op = 1else:flag_op = 0
修改后见下:
arith_lst = arithmetic.combin_num_opera(n)length = len(arith_lst)for x in range(0,length):if(x % 2 == 0):count_num = count_num + 1if(isinstance(x,int) or x<1):flag_num = 1else:flag_num = 0 else:count_op = count_op + 1if(arith_lst[x] in op):flag_op = 1else:flag_op = 0
测试错误2:
出现错误,显示操作数个数并非等于运算符个数加1。在审查代码后发现,在计算操作数及运算符个数时,错把下标当做从1开始。
测试通过:
6.遇到的问题
(1)如何在python中输出“X”, “÷”?
在代码前加入“# coding=gbk”, unicode、gbk、gb2312是编码字符集(后来发现使用这种编码会使注释报错,应该为#coding=utf-8),参考文档[3]
(2)需要考虑运算符优先级?
解决:对运算符进行优先级设置,尝试使用栈进行式子的运算。
在此处出现了小插曲:无意中查到python中内置函数eval()可以对公式字符串进行计算且其直接考虑了运算符的优先级,于是投机取巧地想要通过该方法直接对多运算符算式进行计算,但后来出现一个问题就是,在将分数转化为字符串后,使用eval()进行计算时,该函数会自动忽略分数,而只进行整数的运算。期初是以为自己未将分数转化为字符串,多次尝试,最后发现在存在分数的情况下不能使用该方法。因此折腾了几个小时。
(3)在计算四则运算式的结果时,进行除法时判断两边数字是否是整数?
出现过问题,如果不判断,4÷5就会被算成0,于是需要对除号两边的数的类型进行判断,如果都是整数则直接使用Fraction将除法转化成分数,否则便可直接运算。通常判断变量类型使用的是type()方法,但在参阅参考文档[4]发现,使用isinstance(a, int)方法会较好,该方法返回值为true或者false。
(4)在控制台运行出现了符号的乱码?
期初以为是自己代码的问题,在自己多次尝试以及请教他人之后发现该问题仍不能解决。后来无意中发现在使用win10系统进行运行时,并不会出现乱码,而使用win7系统运行便会出现乱码。
7.项目总结
在第一次作业中也提到,过去大学四年其实代码量并不多,唯一能算代码比较多的就是基于Visual Studio开发的学生信息管理系统了。基于控制台的代码算是很少了。
并且之前使用python编写的代码都是用来进行数据处理或者文件处理,而这次作业选择使用这门语言,也算是经历了一路的波折。
在阅读了邹欣老师的《构建之法》之后,学到了使用PSP方法来更好的对开发一个项目进行合理地规划。同时也知道了在完成一个项目时需要先对项目的需求进行详细地分析。在进行代码实现时,先对基本功能进行实现,再在此基础上进行功能的扩展。慢慢地才能把一个项目合理地完成好。
在本次作业的完成过程中还是遇到了很多麻烦,尤其需要自己牢记的一个教训就是:不要投机取巧!因为侥幸地以为存在已构建好的方法可以直接处理运算式,而浪费了好几个小时进行错误的查找和修改。最终还是使用自己最初构想的利用逆波兰式处理算式优先级的方法。当然这次作业完成进度慢的最主要原因就是,自己之前练习的太少,导致在编写代码时需要经常查阅资料。以及自己在编写代码时太过粗心,总是在小地方出现错误。希望自己能够认真地对待每一次作业,并且能够变得更加细心,努力提高自己的编程水平,就像老师说的:Practice makes perfect!