张宵 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

提交记录:

  

 

posted @ 2020-10-06 18:51  张宵  阅读(196)  评论(1编辑  收藏  举报