张宵 20200924-5 四则运算试题生成
要求1 参考《构建之法》第4章两人合作,结对编程上述功能,要求每人发布随笔1篇 (代码是共同完成的,随笔有以下两种方式:(①允许两人写一份,得一份分数,二人均分;②如果每人写一份,内容不得抄袭,体会需要是自己的,分别得到自己博客的分数)。 (10分)
(1) 给出每个功能的重点、难点、编程收获。
- 功能1:四则运算
重点及难点:随机出题
编程收获:我们是把所有的功能点看完之后,整体编程的
- 功能2:支持括号
重点及难点:
(1)括号的生成
题意共有4个操作数,括号只能出现1个或两个,完全可以列举出所有出现的情况。但是为了迎接挑战,我们采取了自动生成括号的方式。
1.分析可知:括号分为左右两种情况,右括号始终在操作数之后,左括号始终在操作数之前,4个操作数最多可以有3对括号
2.当生成一个操作数后,紧接着的可能是操作符或者右括号
3.可以分两组生成,一组操作数或者“(”;一组操作符或者”)“
4.当左括号小于2生成操作数或”(“,否则只生成操作数;当右括号少于左括号生成操作符和右括号,否则生成操作符
1 /* 2 * 生成方程 3 * pequa:存储方程的结构体 4 */ 5 void rand_equa(pEqua pequa) 6 { 7 int left = 0; //记录左括号 8 int right = 0; //记录右括号 9 int index = 0; //记录方程当前存储的位置 10 bool bol = true; //true生成数字,false生成操作符 11 //srand((unsigned)time(NULL)); 12 for (int i = 0; i < EQUATION_LENGTH;) //每生成一次数字,生成一次操作符 13 { 14 if (bol) 15 { 16 //生成数字或“(” 17 if (left < 2) //左括号小于2生成数字或括号,否则只生成数字 18 { 19 LARGE_INTEGER seed; //设置随机种子,毫秒级 20 QueryPerformanceFrequency(&seed); 21 QueryPerformanceCounter(&seed); 22 srand(seed.QuadPart); 23 24 int rad = rand() % 4; 25 if (rad == 2 && bol) 26 { 27 strcpy_s(pequa->equa[index++], DATA_SIZE, "("); 28 left++; 29 bol = true; 30 continue; 31 } 32 else 33 { 34 strcpy_s(pequa->equa[index++], DATA_SIZE, rand_numb()); 35 i++; 36 bol = false; 37 continue; 38 } 39 } 40 else 41 { 42 //pequa->equa[index++] = rand_numb(false); 43 //char* pChr = rand_numb(false); 44 strcpy_s(pequa->equa[index++], DATA_SIZE, rand_numb()); 45 i++; 46 bol = false; 47 continue; 48 } 49 } 50 else 51 { 52 //生成操作符 53 if (left > right) //右括号少于左括号生成操作符和右括号,否则生成操作符 54 { 55 LARGE_INTEGER seed; 56 QueryPerformanceFrequency(&seed); 57 QueryPerformanceCounter(&seed); 58 srand(seed.QuadPart); 59 if ((rand() % 3) == 2 && bol) 60 { 61 strcpy_s(pequa->equa[index++], DATA_SIZE, ")"); 62 right++; 63 bol = false; 64 } 65 else 66 { 67 LARGE_INTEGER seed; 68 QueryPerformanceFrequency(&seed); 69 QueryPerformanceCounter(&seed); 70 srand(seed.QuadPart); 71 int number = rand() % 4; 72 if (number == 0) 73 { 74 strcpy_s(pequa->equa[index++], DATA_SIZE, "+"); 75 } 76 else if (number == 1) 77 { 78 strcpy_s(pequa->equa[index++], DATA_SIZE, "-"); 79 } 80 else if (number == 2) 81 { 82 strcpy_s(pequa->equa[index++], DATA_SIZE, "*"); 83 } 84 else 85 { 86 strcpy_s(pequa->equa[index++], DATA_SIZE, "/"); 87 } 88 bol = true; 89 } 90 } 91 else 92 { 93 LARGE_INTEGER seed; 94 QueryPerformanceFrequency(&seed); 95 QueryPerformanceCounter(&seed); 96 srand(seed.QuadPart); 97 int number = rand() % 4; 98 if (number == 0) 99 { 100 strcpy_s(pequa->equa[index++], DATA_SIZE, "+"); 101 } 102 else if (number == 1) 103 { 104 strcpy_s(pequa->equa[index++], DATA_SIZE, "-"); 105 } 106 else if (number == 2) 107 { 108 strcpy_s(pequa->equa[index++], DATA_SIZE, "*"); 109 } 110 else 111 { 112 strcpy_s(pequa->equa[index++], DATA_SIZE, "/"); 113 } 114 bol = true; 115 } 116 } 117 } 118 while (right < left) 119 { 120 //char pChr[] = ")"; 121 //pequa->equa[index++] = pChr; 122 strcpy_s(pequa->equa[index++], DATA_SIZE, ")"); 123 right++; 124 } 125 }
(2)运算优先级
设定操作数、”+“或者”-“、”*“或”/“、”(“、”)“的优先级分别为0,1,2,3,4
1.标记待入栈的优先级
2.如果是数字和左括号入栈;如果是右括号,先计算括号内的值,再出栈左括号;如果为 + - ,计算后三个值再入栈,符号入栈;如果为 * / ,并且前一位运算符存在且前一位运算符为 * /,就进行运算。
1 /* 2 * 计算方程结果 3 * equa:方程式 4 * 返回char* 5 */ 6 char* calculate_equa(pEqua equa) 7 { 8 calculate calc; //定义计算式子的栈 9 for (int i = 0; i < DATA_SIZE; i++) 10 { 11 //char* chr = equa->equa[i]; //取一个符号 12 if (strlen(equa->equa[i]) > 10 || strlen(equa->equa[i]) < 1 || equa->equa[i] == "" || equa->equa[i] == "\0") //判定数组数据无效 13 { 14 if (calc.rear == 0) 15 { 16 return calc.equa[0]; 17 } 18 //return calculate_three_result(&calc); //取到末尾结束 19 if (calc.rear == 2) 20 { 21 return calculate_three_result(&calc); 22 } 23 if (calc.rear == 4) 24 { 25 char pChr[DATA_SIZE]; 26 strcpy_s(pChr, DATA_SIZE, calculate_three_result(&calc)); 27 calculate_in(&calc, pChr); 28 return calculate_three_result(&calc); 29 } 30 /*while (calc.rear) 31 { 32 33 }*/ 34 return NULL; 35 } 36 char chr[DATA_SIZE]; 37 strcpy_s(chr, DATA_SIZE, equa->equa[i]); 38 int flag = judge_flag(chr); //记录待入栈符号的标记 39 if (flag == 0 || flag == 3) 40 { 41 calculate_in(&calc, chr); //如果是数字或左括号入栈 42 continue; 43 } 44 if (flag == 4) 45 { 46 //char* ch = calculate_three_result(&calc); //如果是右括号,计算括号内的值,再出栈左括号 47 char pCh[DATA_SIZE]; 48 if (calc.flag[calc.rear - 1] == 3) 49 { 50 strcpy_s(pCh, DATA_SIZE, calculate_out(&calc)); 51 calculate_out(&calc); 52 calculate_in(&calc, pCh); 53 continue; 54 } 55 strcpy_s(pCh, DATA_SIZE, calculate_three_result(&calc)); 56 if (calc.flag[calc.rear] == 3) 57 { 58 calculate_out(&calc); 59 calculate_in(&calc, pCh); //计算结果入栈 60 continue; 61 } 62 calculate_in(&calc, pCh); //计算结果入栈 63 //char* pCh = calculate_three_result(&calc); 64 strcpy_s(pCh, DATA_SIZE, calculate_three_result(&calc)); 65 calculate_out(&calc); 66 calculate_in(&calc, pCh); //计算结果入栈 67 continue; 68 } 69 if (flag == 1 && calc.rear >= 2 && calc.flag[calc.rear - 1] >= 1 && calc.flag[calc.rear - 1] <= 2) //如果为 + - ,计算后三个值,再入栈,符号入栈 70 { 71 //char* chr = calculate_three_result(&calc); 72 char pCh[DATA_SIZE]; 73 strcpy_s(pCh, DATA_SIZE, calculate_three_result(&calc)); 74 calculate_in(&calc, pCh); 75 calculate_in(&calc, chr); 76 continue; 77 } 78 if (flag == 2 && calc.rear > 2 && calc.flag[calc.rear - 1] == 2) //如果为 * / ,并且前一位运算符存在,前一位运算符为 * /,就进行运算 79 { 80 //char* chr = calculate_three_result(&calc); 81 char pCh[DATA_SIZE]; 82 strcpy_s(pCh, DATA_SIZE, calculate_three_result(&calc)); 83 calculate_in(&calc, pCh); //计算结果入栈 84 calculate_in(&calc, chr); //操作符入栈 85 continue; 86 } 87 calculate_in(&calc, chr); 88 } 89 return NULL; 90 }
编程收获:
上面这一方法似乎很麻烦,在这之前提出了经典的逆波兰方式,但是韩亚光同学的这一想法让我很吃惊,之前没有学习过,所以秉持着向他学习的理念,最终采用了这一方式。虽然逆波兰方式最终没有采用,但是加深了我对这一块的知识理解及栈的应用。在作业过程中我体会到基础知识是格外重要的,以往对数据结构的学习都是停留在理解上,看到的也是伪代码,很少真正的运用,所以在今后的学习中要加强这一方面。
- 功能3:限定题目数量,"精美"打印输出,避免重复
重点:命令行参数
编程收获:由于之前的作业都涉及到了命令行参数,在这里只属于重点但不属于难点了。熟能生巧,多操练学习很多过往的难点都会被解决。
if (argc == 3 && strcmp(argv[1], "-c") == 0) { for (int i = 0; i < strlen(argv[2]); i++) { if (argv[2][i] > '9' || argv[2][i] < '0') { printf("题目数量必须是 正整数。\n"); return 0; } } int num = atoi(argv[2]); //题目数量 pEqua pequ = create_equa_array(num);
- 功能4. 支持分数出题和运算
重点及难点:
(1)带分数的出题
(1)带分数题目的计算
分数生成这里定义了一个分数类,随机生成两个数,构造成一个分数,计算运用到了符号重载,最小公倍数,最大公倍数等方法。
1 fraction fraction::reduction() 2 { 3 int nume = abs(this->numerator); //获取分子 4 int deno = abs(this->denominator); //获取分母 5 if (nume == 0) 6 { 7 this->denominator = 0; 8 return *this; 9 } 10 int g = gcd(nume, deno); //获得分子和分母的最大公约数 11 nume = nume / g; 12 deno = deno / g; 13 //判断数据正负号 14 if ((this->numerator < 0 && this->denominator < 0) || (this->numerator > 0 && this->denominator > 0)) 15 { 16 this->numerator = nume; 17 this->denominator = deno; 18 return *this; 19 } 20 else 21 { 22 this->numerator = -1 * nume; 23 this->denominator = deno; 24 return *this; 25 } 26 }
(2)给出结对编程的体会
因为本人编程能力不足,许多语法都不清楚,特别感谢本次结对编程搭档--韩亚光同学的耐心解答。
首先,结对编程重在沟通。两个人编程和一个人编程的最大区别是需要与队友沟通,明确团队任务的内容及进度。在这一点上,我和韩亚光同学都是比较积极的,希望能尽早完成。在最初,韩亚光同学提出这个题他的思路的时候,我本人听的一知半解,但是他通过腾讯会议给我梳理了一遍他的思路及想法,使我确切的知晓了他编程的思路。在一些重难点上面我们也进行了有效的讨论及沟通,这一点我们团队做的较好。
其次,结对编程促进学习。每个人写代码都会有自己特定的风格,我们在编程之初就约定了编程规范,并在编程过程中互相提醒,这就在互相发现问题并及时得到解决。在四则运算这个编程中,我们两人有两种不同的实现方式,互相学习了对方的思想,为之后学习编程提供了新的方法。
最后,结对编程提高效率。当发现自己是团队中的一员时,总会要求自己不能拖后腿,按照约定时间完成项目进度,尽力发挥团队一员的作用。
(3) 至少5项在编码、争论、复审等活动中花费时间较长,给你较大收获的事件。
1.带括号算术式生成规则
在这里,我们有不同想法,我认为可以穷举出括号的情况,直接填充生成;韩亚光认为如果之后有多余4个操作数的情况呢,提出了更优化的方案。这里我学习到了他的思想,不要急于求成,仔细观察表达式,在总结通用方法。
2.算数式计算
经典算法是将中缀表达式转换为后缀表达式,在对后缀表达式求解。我们在这里采用了一种不那么机智的办法,但是很新颖,总体思想和经典算法契合,采用标记法来判断当前栈的后三位是否需要计算。这里我们列举处理许多情况来验证是否可行,也花费了相当大的精力来完善。
3.单元测试
对单元测试框架、测试用例的编写不熟悉,花费了一定时间来学习。按照功能点设计的测试用例,韩亚光同学认为测试用例要多一点,我认为测试用例只需要包含到测试点就好,比如测试+、-、*、/这四个运算符,可以只有写两个测试用例即可,分数需要测到正分数、负分数、假分数,在于测试用例测的点,不用测很多组。
4.分数计算
韩亚光同学运用了符号重载,将所有的操作数变为分数再计算,这一点我认为他用的很巧妙,非常值得我学习。在分数这块,韩同学耗费了相当大的精力,因为最初的方案就是他想出来的,在实现的过程中遇到了许多问题,比如生成的方程式全是分数形式,这一点我们尚未优化。
5.数组越界
对字符数组的运用不熟练,导致相当一部分的错误是由于数组越界引起的。同时对字符指针有了更深的理解。指针这块是我的薄弱点,后续需要继续深入了解及运用。
要求2 给出结对开发的截图证据,要求截图能够证明你们是在结对编程。 (5分)
编程地点:计算机东楼220
团队成员:张宵、韩亚光
要求3 使用coding.net做版本控制。checkin 前要求清理 临时文件、可执行程序,通常执行 build-clean可以达到效果。(25分)
作业地址:https://e.coding.net/nenuwork/ourteam/arithmetic_operation.git
提交记录: