软件工程第3次作业—四则运算(结对作业)
作业要求的博客链接:https://edu.cnblogs.com/campus/nenu/2016CS/homework/2266
git仓库地址:https://git.coding.net/pipifan/f4.git
本次作业是结对作业,我的结对伙伴是樊友朋同学,他的博客地址是:http://www.cnblogs.com/pipifan/p/9918250.html
项目概要:
本次项目实现的是一个用于四则运算的控制台程序,目前已实现的功能如下:
1)支持整数和不含括号的四则运算且表达式可以重复。
2)支持小数和含小括号的四则运算且表达式可以重复。
3)表达式不重复且输出结果显示在控制台,然后将控制台显示的结果输出到指定位置的txt文件中。
项目详情:
一、本次作业采用c/c++进行编程。首先分析项目要求概括功能:
1)按照控制台输入的N ,生成N道由随机生成的整数与合法运算符组成的四则运算,并判断用户输入答案的对错。(输入 f4 -n N)
2)按照控制台输入的N ,生成N道由随机生成的整数、小数与合法运算符和括号组成的四则运算,并判断用户输入答案的对错。(输入 f4 -c N )
3)按照控制台输入的N 、文件路径M,生成N道由随机生成的整数、小数与合法运算符和括号组成的不重复的四则运算,给出答案打印在控制台,输出到文件M中。(输入 f4 -c N -f M)
4)如果输入的N不合法会输出相应的警告。
二、根据分析规划函数模块:
1)字符串转化为数字。
2)判断运算符号的优先级。
3)处理四则运算表达式,计算表达式的值。
4)处理控制台输入输出,产生随机数,随机运算符号和随机括号。
5)主函数,负责调用各个模块,控制流程。
三、部分模块代码实现过程:
1)处理四则运算表达式。
首先分配两个栈sp,sv,分别存储运算符和运算数字,从左到右读取中缀表达式,遇到操作数就入栈sv,遇到左括号或者当前运算符比sp栈顶优先级高的符号就入栈sp,遇到比sp栈顶优先级低的符号就出栈两个操作数和栈顶操作符,结果再入栈sv,遇到右括号同时栈顶是左括号就出栈左括号。最后sv栈顶就是该表达式的结果。
具体代码如下:
double solve(string s ) { stack<double> sv; stack<char> sp; char c; int k = 0, flag = 1; double x, y; sp.push('\0'); c = s[k]; while (flag) { if (c >= '0'&&c <= '9' || c == '.') { sv.push(toNum(s, k)); } else if (c == '\0'&& sp.top() == '\0') { flag = 0; } else if (c == '(' || (priority(c) > priority(sp.top()))) { sp.push(c); k++; } else if (c == ')'&& sp.top() == '(') { sp.pop(); k++; } else if (priority(c) <= priority(sp.top())) { x = sv.top(); sv.pop(); if(sv.empty()) y = 0; else { y = sv.top(); sv.pop(); } c = sp.top(); if( c == '/' && fabs( x ) <= eps ){ int num = rand() % 3; c = mp[num]; } sp.pop(); switch (c) { case '+':y = x + y; break; case '-':y = y - x; break; case '*':y = x*y; break; case '/':y = y / x; break; } sv.push(y); } c = s[k]; } return sv.top(); }
2)随机生成整数数字和运算符。
实现功能一用rand随机生成数字和运算符(提前存到数组中)。
这里在实现过程中有两点我们进行了思考:其一是单纯用rand()会使每次的随机数一样,这样就不符合随机这个概念,所以我们加上 srand( (int)time(0) ),用时间做随机数的种子,这样可以保证每次的随机数不同。其二是考虑到除数不能为0,所以我们判断如果“/”后面的随机数是0就再造数据直至不为0,保证了表达式的正确性。
s = ""; if( flag == 1 ){ for(int i = 0 ; i < 7 ; i++ ){ if( (i % 2) == 0 ){ int num = rand() % 10; int len = s.size(); while( len > 0 && num == 0 && s[len - 1] == '/' ) num = rand() % 10; stringstream ss; ss << num; string tmp; ss >> tmp; s += tmp; } else{ int num = rand() % 4; s += mp[num]; } } }
3)随机生成带括号的整数和小数的数据。
生成小数时用两个随机数相除。
这里的难点是随机加括号,我们采取的办法是先随机加左右括号,最后检查是否匹配,缺少匹配的括号就在表达式首尾加上相应的括号。
for(int i = 0 ; i < 7 ; i++ ){ string tmp; if( (i % 2) == 0 ){ int num = rand() % 10; if( (num % 3) == 0 && num > 0 ){ int num1 = rand() % 100; double tmp_num = num1*1.0 / 10; stringstream ss; ss << tmp_num; ss >> tmp; x[xx++] = tmp_num; } else{ int len = s.size(); while( len > 0 && num == 0 && s[len - 1] == '/' ) num = rand() % 10; stringstream ss; ss << num; ss >> tmp; x[xx++] = num*1.0; } if( num < 3 && i < 6 && i > 0 ){ s += "("; cnt1++; } s += tmp; if( num > 6 && i > 0 && i < 6){ if( cnt1 > 0 ) cnt1--; else cnt2++; s += ")"; } } else{ int num = rand() % 4; s += mp[num]; } }
四、测试阶段:
1)功能1
2)功能2
3)功能3
五、项目进行过程
1.给出结对编程的体会
结对编程的好处是可以集思广益,尤其针对本次作业的一些小的细节可以做到查漏补缺。
比如输入参数错误时的文字提示,功能一和功能二的提示是不同的;比如除数不能为0;比如保留小数的位数时要考虑是否为有限小数,无限小数要保留三位;比如功能三输出时要对齐......等等的一些问题。在解决功能二中如何随机加括号,并且如何匹配正确时,两个人也进行了各自的思考然后加以讨论得出解决方案。在查找相应的参考资料时也更便捷。
由于结对编程需要共用一个设备,所以编程以平时写代码能力比较强的樊友朋同学为主,相当于Driver,我负责收集整理资料、提供思路,发现、修改细节问题和测试数据,相当于Navigator。我对结对编程的体会应该是分清主次,各司其职,需要默契,在讨论中解决问题和完善项目。在《构建之法》中也提到过结对编程的好处,“结对能带来更多的信心,高质量的产出可以带来更高的满足感。”,通过这次结对作业,确实感受的优秀的队友带来的不同的合作感,更加有利于问题的解决。
2. 至少3项在编码、争论等活动中花费时间较长,给你较大收获的事件。
1). 用栈处理四则运算表达式。具体思路可以参考3.1
2). 随机生成小数和括号。主要的难点是随机加括号,并且要保证括号的正确性。具体思路参考3.3
3). 功能二和三要求表达式不重复。初看作业要求中的不重复概念是要运用交换律、结合律等数学定理来检测题的不重复性,但是这样比较麻烦。所以经过讨论后,我的伙伴提出为了保证题的不重复,可以检查每个题中的数字是否相同。因为题目本身是由随机数、随机括号、随机运算符组成的,它重复的概率很低,所以我们把表达式的数字排序,包装放在set里面,检查数字重复时就重新生成表达式,这样不会影响随机性,也不会有重复的表达式,最重要的是降低算法复杂度(相对检查交换律的那种算法)
4). 花费时间长的地方还有5.1提到的细节问题,为了尽力满足作业要求,我们用了大概两至三个小时去修改细节。
六、给出照片1张,包括结对的2位同学、工作地点、计算机,可选项包括其他能表达结对编程工作经历的物品或场景。