2017年秋季学期软件工程第二次作业

2017年秋季学期软件工程第二次作业

GITHUB地址

https://github.com/yangliangrym/Arthmetic-yl

PSP

PSP2.1  Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 40 40
Estimate 估计这个任务需要多少时间 1200 1400
Development 开发 680 760
Analysis 需求分析 20 30
Design Spec 生成设计文档 30 60
Design Review 设计复审 60 80
Coding Standard 代码规范 20 30
Design 具体设计 60 60
Coding 具体编码 100 140
Code Review 代码复审 20 30
Test 测试(自我测试,修改代码,提交修改) 10 10
Reporting 报告 60 60
Test Report 测试报告 30 30
Size Measurement 计算工作量 10 10
Postmortem&Process Improvement Plan 事后总结,并提出过程改进计划 60 60
合计   1200 1400

 

 

 

 

 

 

 

 

 

 

 

 

 

题目描述

从《构建之法》第一章的 “程序” 例子出发,完成一个能自动生成小学四则运算题目的命令行 “软件”,满足以下需求:

  1. 参与运算的操作数(operands)除了100以内的整数以外,还要支持真分数的四则运算,例如:1/6 + 1/8 = 7/24。操作数必须随机生成。
  2. 运算符(operators)为 +, −, ×, ÷ (如运算符个数固定,则不得小于3)运算符的种类和顺序必须随机生成。
  3. 要求能处理用户的输入,并判断对错,打分统计正确率。
  4. 使用 -n 参数控制生成题目的个数,或通过用户交互过程确定题目个数,例如执行下面命令将生成5个题目
    • (以C/C++/C#为例) calgen.exe -n 5
    • (以python为例) python3 calgen.py -n 5

附加功能(算附加分)

  1. 运算符个数随机生成(考虑小学生运算复杂度,范围在1~10)
  2. 设计并实现一种合理的根据题目难度分配不同分值的方案
  3. 设计并实现一种计时机制,能够实现超时停止答题,或者根据答题用时调整分数
  4. 防止随机生成的题目发生重复

题目分析与初步思路

看到题目后整个人处于无从下手的状态,因为C++已经是大一时候学的了,快4年没有碰了几乎全忘了,那天下课之后我就立刻去图书馆借了两本书一本是《C++实用手册》,一本是《由浅入深学C++》,顺便也把《人件》这本书也借了回来,回来之后就开始从头开看,因为之前有过基础,所以大概看了两天不到三天,每天大概6小时吧书大概看了一遍。

利用结构数组存放操作数:看的过程中也在分析题目,题目中说除了要支持整数运算外还需要支持真分数运算,整数运算比较容易实现,真分数计算就不太容易了,一开始我想的是将整数和分数分别用两个函数生成,用rand函数随机控制是生成整数还是分数,直到我看到了书上的复杂数据结构中的结构数组我才想到整数和分数可以用同一个函数生成,并同时存放在结构数组中,只不过整数部分的分母为1,cout时只输出分子就行了。真分数的计算还设计到化简,所以还必须利用最大公因式化简,所以还需要定义求最大公因式函数。

利用字符数组存放操作符并随机访问:题目中还要求运算符种类和数量随机生成,数量随机生成只需控制生成算式函数体的循环次数就可以实现,种类随机生成,可以吧操作符存放在字符数组里,利用rand()%4随机访问数组里的元素,由于要采用+, −, ×, ÷ 这四种运算符,但是 ×, ÷这两个运算符计算机无法识别,所以在随机生成运算符的时候还需用 *和 / 代替。

利用后缀表达式计算四则运算:处理用户输入并判断对错,这就需要对算式进行计算,而四则运算需要考虑优先级这一为,所以这一步比较难,上网查到可以利用把算式的中缀表示改成后缀表示就可以计算,具体过程如下:

用后缀表达式计算四则运算算法

http://blog.csdn.net/chuchus/article/details/70576037

1*2+3*4, 这个对应的中缀表达式就是[1,*,2,+,3,*,4], 需要转换成后缀表达式[1,2,*,3,4,*,+], 才能进一步计算得到结果.

1.得到后缀表达式

需要准备两个容器, 符号栈 operatorStack 与 盛放后缀表达式的队列 expQueue. 
按照下列规则从前到后遍历中缀表达式.

  1. 遇到数字num, 就expQueue.push(num);
  2. 若遇到操作符, 分两步处理 
    2.1 在符号栈operatorStack 中弹出若干个操作符op, 直至遇到优先级更低的操作符, 做expQueue.push(op)操作. 
    2.2 将当前运算符op放入栈中, operatorStack.push(op)

2.计算后缀表达式

需要用到上面得到的后缀表达式expQueue, 还需要一个保存计算结果的数字栈 numStack. 
按照下列规则从前到后遍历后缀表达式.

  1. 遇到数字num, numStack.push(num);
  2. 若遇到操作符op, 从numStack取出栈尾两个数字,b=numStack.pop(),a=numStack.pop() 得到c=a op b, numStack.push(c).

不断进行上述操作, 最后只剩一个数字, 就是答案.

该方法虽然说是要用栈处理,但是由于一开始就没怎么学过栈的用法,所以想到可以利用数组的方法处理上述问题,把操作数和操作符分别存放在相应的数组里面,另外需要一个存放操作数和操作符相对位置的一个数组,只需要把相应操作数在结构数组中的坐标+‘0’转化为字符型存放在字符数组中就可以,访问时判断是否在字符‘0’到字符‘:’之间就可以(由于最多操作符是10个,操作数最多就是11个,而‘:’的ASCII码是58,而‘0’的ASCII码为48,刚好可以利用访问11个操作数)判断是操作数还是操作符了,然后进行四则运算,所以还需要定义计算函数。

附加要求的实现:

  1. 运算符个数随机生成:这个前面已经说过了,只需要rand()%10+1就可以控制出运算符个数,利用for循环依次打印。
  2. 合理的分数分配方案:我是利用没到算式里面的运算符个数分配分数的,分别统计多个运算式中的运算符总数和答对的运算符总数按比例分配百分制分数score = ro(100 * r_op_N / op_N)。
  3. 计时停止或通过计时调整分数:我选择的是比较简单的计时调整分数,因为我查到要想计时停止可能需要运用到多线程处理的知识,因为时间有限,而且之前也没接触过,所以没有用实现计时停止,但是可以把每道题的答题时间和预定时间(运算符个数*10秒)进行比较,超时就不计算分数。
  4. 防止随机题目重复:这一项目前还不清楚怎么实现,除非把之前的题目都保存,没出一道题目与之前的题目进行比较,如果相同就重新出题,但由于我的程序里出完一道题,结构数组里的数据就被清空了,所以这一附加要求还没有实现。
  5. 另外我还添加了答完题用户可以选择输入‘y’重新继续答题或者退出的代码。

设计实现过程:

  1. 定义结构数组存放操作数的分子与分母。函数名: fraction
  2. 定义存放四个运算符的数组。                   数组名:ope[4]
  3. 运算符优先级判断函数。                     函数名:Priority(char op)
  4. 最大公约数函数。                                      函数名:gcd(int a, int b)
  5. 求两个操作数计算结果的函数。               函数名:Ca(struct fraction n1, struct fraction n2, char op)
  6. 随机生成运算符的函数。                          函数名: getop()
  7. 随机生成操作数函数。                              函数名:getN()
  8. 保留一位小数函数。                                  函数名:ro(double r)
  9. 主计算函数。                                             函数名:char m()
  10. 主函数。                                                     函数名:main()

其中最难理解的就是主计算函数中的根据优先级判断入栈和求解过程(按照一维数组解释,结构数组同理)。1)优先级入栈:首先生成一个随机数x1存入操作数数组opN[11]={x1},并在字符数组expQueue中存入字符‘0’,代表opN[0]=x1,生成一个操作符op1存放在符号数组operatorStack[20]中,再生成一个随机数存入opN[11]={x1,x2},并在字符数组expQueue中存入字符‘1’,代表opN[1]=x2,再生成一个操作符op2,即算式为x1,op1,x2,op2....,如果op2优先级比op1小,所以先算x1op1x2,所以此时把op1存入字符数组expQueue中,此时expQueue[30]={'0','1',op1},把op2存到operatorStack[20]中的op1位置,然后再生成随机数与操作符按照上面方法存入到操作数数组opN[11]和字符数组expQueue中,最后按照后缀表达式进行求解。

重要代码说明:

for (int j = 0;j<n;j++) {
        cout << "第" << j + 1 << "题" << endl;
        int t = 0, a = 0, b = 0, top = 0;
        double cost = 0;//用来记录每次算题所用时间,与预定时间进行比较
        struct fraction opN[11], numStack[11];//分别用来存放操作数与后缀表达式计算结果
        char op, operatorStack[20], expQueue[30];//分别代表运算符,运算符储存数组,运算符与操作数坐标储存数组
        opN[a++] = getN();//获取操作数存放在opN中
        expQueue[b++] = a - 1 + '0';//将整型转化为字符型存于字符数组中代表操作数在opN中坐标,以此记录运算符与操作数的顺序。
                                    //加入附加功能,运算符个数随机生成
        int op_Num = rand() % 9 + 1;//随机生成操作符的数量(1-10)
        for (t = 0;t<op_Num;t++)//通过循环生成算式
        {
            op = getop();  //生成运算符
if (t == 0) { operatorStack[top++] = op; opN[a++] = getN(); expQueue[b++] = a - 1 + '0'; continue; //跳出循环,第一次因为不需要进行优先级比较,所以直接返回上一级for循环 } //根据运算符优先级排序,控制出栈顺序,如果当前的操作符优先级比上一个优先级小于或者等于,就把上一个运算符放到expQuue中 while (Priority(op) <= Priority(operatorStack[top - 1]) && top>0) { top--; expQueue[b++] = operatorStack[top]; } operatorStack[top++] = op;//操作符放到字符数组中方便进行比较运算符优先级以及存储操作符 opN[a++] = getN();//产生一个随机数 expQueue[b++] = a - 1 + '0';//将整型转化为字符型存于字符数组中,记录操作数坐标 } //将剩余的低优先级的依次取出放入expqueue中 while (top>0) { top--; expQueue[b++] = operatorStack[top]; } //根据后缀表达式计算结果 top = 0; for (int i = 0;i<b;i++) {//如果当前的expqueue中的元素ASCII码在‘0’到‘:’之间,就可以认定是操作数的坐标,把相应操作数提取出来放到numStack中 if (expQueue[i] >= '0'&&expQueue[i] <= ':')// { int ch = expQueue[i] - '0';//将存入的字符转化为整型的数,其值就是相应操作数在opNum中的坐标 numStack[top].a = opN[ch].a; numStack[top].b = opN[ch].b; top++; } else {//如果不是上述,就是相应的操作符,把上面存到numStack中的操作数进行相应计算,最后得到的就是所要求得结果 top--; numStack[top - 1] = Ca(numStack[top - 1], numStack[top], expQueue[i]); } }

 

测试运行:

本次运行检查了输入答题数量,不满足要求时重新输入,其中有回答正确的但是超时了,所以不计算成绩,有回答正确的,还有回答错误的,程序能正常运行。

项目总结:

本次作业感觉自己完全是用这一周的时间把C++从头到尾学了一遍,感觉这一周是自来到海工以来是最忙碌的一周,忙到自己午饭没吃,午觉也没睡,因为感觉不把这些搞定自己心慌。自己在图书馆借的两本书一本更容易理解,另外一本内容比较详细,两本书差不多都看了一遍。因为上周末有考试,所以是从这周一才开始做作业,每天至少花费8个小时来学习C++,一开始一方面由于左手手指受伤不方便敲代码,另外一方面是自己在草稿纸上演练更容易理解,所以从开始学C++,到开始做作业的题目分析及其实现代码全部是在草稿纸上完成的,所以到本周四已经用掉了20张草稿纸了。总的来看感觉自己进步很大,一开始题目下来的时候自己完全不知道该怎么做,遇到很多问题,但最终通过自己看书,上网查相关资料,问老师,问同学把这些问题全部解决了。现在觉得不管是自己觉得有多难的问题,只要自己肯花时间,肯用心去做一件事最终都能够完成。

通过本次作业我体会到解决同一种问题可能有很多种方法,每个人的想法都不一样,作为一个编程新手,可以有自己的想法,但是要多看看前辈们是怎么解决这些问题的,学会用程序的方法解决这些问题,而且尽量的把问题想得全面。可能在编程过程中会出现要令自己崩溃的事情,但是不能慌张,冷静分析才是解决问题的最快方法。

另外本作业有BUG存在,就是用户输入的数据类型是char型,用来做对比的正确答案也是char型,一旦用户把答案从分数写成小数的形式,程序就会误判,本想把数据类型定义为double类型,但是在用户输入分数形式的数的时候又会出现更严重的BUG,所以只能暂时用当前的程序了,后面会尽量想办法把这一问题解决。

 

posted @ 2017-12-29 17:01  面朝大海杨亮  阅读(217)  评论(2)    收藏  举报