【结对编程】四则运算生成器

结对项目报告

源码及接口说明地址:https://github.com/hzphzp/ArithmeticCore/tree/master/ArithmeticCore

一、功能实现

按照用户设置的要求,随机生成一定数量的小学四则运算算术表达式及其答案。用户可以选择整数、小数、分数三种运算式,可以设置题目的精度、运算符种类及数目等。我们小组负责计算核心模块,生成链接库供给UI组调用。

二、需求分析

1.计算核心(Core)

属性设置:用户可以通过UI界面设置算术表达式属性,包括表达式种类(整数式、小数式、分数式)、题目数量、数值范围、数值精度、运算符数目、运算符种类(+-*/^).

题目生成:随机生成满足用户要求的算术题(表达式),显示给用户。

题目运算:将生成的题目进行运算,遇到异常表达式时须做异常处理,保存结果 。

2.用户界面(UI):

另有小组负责,在此不赘述。可参看博客:

3.接口(API):

将core模块转化为动态链接库.dll或者静态链接库.lib,提供给UI调用。

三、设计实现

按照面向对象的思想 ,作为Core组,我们设计了一个Core类,这个类下包含了一个结构体对象config,三个public方法:Calc(),setting(),generate()。

1.Config:

保存各项属性,传递给setting()

2.Calc():

调用了大量子函数,大致分类如下:

3.setting()

分为两个重载的方法:利用option来区分是设置题目数量还是操作数数量还是操作数上下限,或者利用option来区分是什么运算符,key来确定是否加入。

4.generate()

a.根据config的设置来随机的生成没有括号的表达式,然后递归调用私有方法AddBracket(), 随机的生成合法的并列或者是嵌套的括弧

没进入一次递归,就生成表达式的一部分,并且将这部分的表达式的后缀表达式和原表达式相同部分的后缀表达式进行比较,

如果相同,则说明这次增加的括弧多余,作废后,继续递归

b.产生括弧的式子和原表达式同时生成后缀表达式,如果后缀表达式相同,则括号

并且将表达式尝试传入Calc方法,如果抛出不整除,除零,越界,幂指数不为整数等异常,就进行处理,放弃这个表达式,重新生成。每生成一个表达式,需要和之前生成的所有表达式进行运算过程比较,即传入ProblemCompare方法,两个表达式同时进行计算,如果每次计算的两个操作室相同(不计入顺序), 操作符相同,则两个表达式为同一表达式。

四、核心代码

1.结构体变量定义

 1 typedef struct
 2 {
 3     int ProblemNum;
 4     int precision;
 5     int OperandNum;
 6     int lrange;
 7     int hrange;
 8     int calculate_kind;
 9     string KindofOperator;
10 }Config;
config

2.Calc方法

core.Calc(string expression);

调用了许多子函数,核心有将中缀表达式转化为后缀表达式的函数单步运算函数利用栈的运算函数,代码如下:

a.整数式和小数式

 1 // transform integral expression to suffix
 2 string exp2suffix(string exp){
 3     stack<char> s;
 4     string sur;
 5     int i;
 6     char w;
 7     sur;
 8 
 9     // travel loop
10     for (i = 0; i < exp.size(); i++){
11         if (isdigit(exp[i]) || exp[i] == '.'){
12             while (isdigit(exp[i]) || exp[i] == '.')
13             {
14                 sur += exp[i++];
15             }
16             i--;
17             sur += ' ';
18         }
19         // if it is + or -
20         else if (isone(exp[i]))
21         {
22             while (s.size() && (isone(s.top()) || istwo(s.top()) || isthree(s.top())))
23             {
24                 sur += s.top();
25                 s.pop();
26             }
27             s.push(exp[i]);
28         }
29         // if it is ( or )
30         else if (exp[i] == ')')
31         {
32             while (s.top() != '(')
33             {
34                 sur += s.top();
35                 s.pop();
36             }
37             s.pop();
38         }
39         // if it is * or /
40         else if (istwo(exp[i])){
41             while (s.size() && (istwo(s.top()) || isthree(s.top())))
42             {
43                 sur += s.top();
44                 s.pop();
45             }
46             s.push(exp[i]);
47         }
48         // if it is ^
49         else if (isthree(exp[i]))
50         {
51             while (s.size() && isthree(s.top()))
52             {
53                 sur += s.top();
54                 s.pop();
55             }
56             s.push(exp[i]);
57         }
58         // other character('(')
59         else
60         {
61             s.push(exp[i]);
62         }
63     }
64     while (s.size())
65     {
66         sur += s.top();
67         s.pop();
68     }
69     return sur;
70 }
str2suffix()
 1 // calcInte a integral expression
 2 double calcInte(string s, int lrange, int hrange){
 3     double operand1;
 4     double operand2;
 5     double result;
 6     stack<double> num;
 7     string temp;
 8     int i;
 9 
10     for (i = 0; i<s.size(); i++){
11         temp = "";
12         if (isdigit(s[i]) || s[i] == '.'){
13             while (isdigit(s[i]) || s[i] == '.')
14             {
15                 temp += s[i++];
16             }//如果最后一位是数字,这样做会出错   
17             num.push(str2double(temp));
18         }
19         else{
20             operand2 = num.top();
21             num.pop();
22             operand1 = num.top();
23             result = oprInte(operand1, s[i], operand2, lrange, hrange);
24             num.pop();
25             num.push(result);
26         }
27     }
28     result = num.top();
29     return result;
30 }
calcInte()

b.分数结构体与分数式

 1 // operate one step of caculation in fraction
 2 fraction oprFrac(fraction opd1, char opt, fraction opd2, int lrange, int hrange)
 3 {
 4     fraction res;
 5     res.denominator = 1;
 6     res.numerator = 1;
 7 
 8     if (opd1.denominator == 0 || opd2.denominator == 0)
 9     {
10         throw "ERROR: denominator can not be 0!";
11     }
12     switch (opt){
13     case '+':
14         res = FracPlus(opd1, opd2);
15         break;
16     case '-':
17         res = FracMinus(opd1, opd2);
18         if (res.numerator < 0)
19         {
20             throw "ERROR: temperary result less than 0" ;
21         }
22         break;
23     case '*':
24         res = FracMul(opd1, opd2);
25         break;
26     case '/':
27         if (opd2.numerator == 0)
28         {
29             throw "ERROR: divisor can not be 0";
30         }
31         else
32         {
33             res = FracDiv(opd1, opd2);
34         }
35         break;
36     case '^':
37         if (opd1.numerator == 0 && opd2.numerator == 0)
38         {
39             throw "ERROR: base 0 can be with an negtive or zeroth index" ;
40         }
41         else
42         {
43             if ((double)opd2.numerator / (double)opd2.denominator - int(opd2.numerator / opd2.denominator) != 0)
44             {
45                 throw "ERROR: this pow can not be calculate easily";
46             }
47             if (opd2.numerator / opd2.denominator > 10 && opd1.numerator / opd1.denominator > 10)
48             {
49                 throw "ERROR: too big number may cause overflow!";
50             }
51             res = FracPow(opd1, opd2);
52         }
53         break;
54     default:
55         throw "ERROR: operator";
56         break;
57     }
58     if (res.numerator < lrange || res.denominator < lrange || res.numerator > hrange ||  res.denominator >hrange)
59     {
60         throw "ERROR: temporary result surpass the range";
61     }
62     return res;
63 }
oprFrac()

c.部分异常处理函数:

  1 // judge the operator
  2 bool isOperator(char ch)
  3 {
  4     if (ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '^')
  5     {
  6         return true;
  7     }
  8     if (ch == '.' || ch == '|')
  9     {
 10         return true;
 11     }
 12     if (isdigit(ch))
 13     {
 14         return false;
 15     }
 16 }
 17 
 18 // match the bracket
 19 bool MatchBracket(string exp)
 20 {
 21     int matchstat = 1;
 22     stack<char> charStack;
 23     
 24     for (int i = 0; i < exp.size(); i++)
 25     {
 26         switch(exp[i])
 27         {
 28         case'(':
 29             charStack.push(exp[i]);
 30             break;
 31         case')':
 32             if (charStack.empty())
 33             {
 34                 matchstat = 0;
 35             }
 36             else if (charStack.top() != '(')
 37             {
 38                 charStack.pop();
 39                 matchstat = 0;
 40             }
 41             else if (charStack.top() == '(')
 42             {
 43                 charStack.pop();
 44             }
 45             break;
 46         default:
 47             break;
 48         }
 49     }
 50     if (matchstat == 1 && charStack.empty())
 51     {
 52         return true;
 53     }
 54     throw "ERROR: bracket matching!";
 55     return false;
 56 }
 57 
 58 // judge exp is one specific kind expression
 59 bool FracOrInte(string exp)
 60 {
 61     string::size_type idx1;
 62     string::size_type idx2;
 63     idx1 = exp.find(".");
 64     idx2 = exp.find("|");
 65     if (idx1 != string::npos && idx2 != string::npos)
 66     {
 67         throw "ERROR: fraction expression can't contain float";
 68     }
 69     return true;
 70 }
 71 // exception char
 72 bool ExceptChar(string exp)
 73 {
 74     for (int i = 0; i < exp.size(); i++)
 75     {
 76         char ch = exp.at(i);
 77         if (!isdigit(ch) && ch != '.' && ch != '|')
 78         {
 79             if (ch != '+' && ch != '-' && ch != '*' && ch != '/' && ch != '^')
 80             {
 81                 if (ch != '(' && ch != ')')
 82                 {
 83                     throw "ERROR: character in this expression!";
 84                 }
 85             }
 86         }
 87     }
 88     return true;
 89 }
 90 
 91 // check out the operator connection 
 92 bool ExceptConnection(string exp)
 93 {
 94     for (int i = 0; i < exp.size(); i++)
 95     {
 96         if (isOperator(exp[i]))
 97         {
 98             // check out the connection between operators
 99             if (i == 0)
100             {
101                 throw "ERROR: operator can not be in the start";
102             }
103             if (i + 1 == exp.size())
104             {
105                 throw "ERROR: operator can not be in the end";
106             }
107             if (exp[i - 1] == '(')
108             {
109                 throw "ERROR: operation connection!";
110             }
111             if (exp[i + 1] == ')')
112             {
113                 throw "ERROR: operation connection!";
114             }
115             if (isOperator(exp[i + 1]))
116             {
117                 throw "ERROR: operation connection!";
118             }
119             if (exp[i] == '.' || exp[i] == '|')
120             {
121                 if (exp[i + 1] == '(' || exp[i+1] == '|' || exp[i + 1] == '.')
122                     throw "ERROR: operation connection!";
123             }
124             // end check
125         }
126     }
127     return true;
128 }
Exception

3.重载的Setting方法

a.core.Setting(string option, int key);

利用option来区分是设置题目数量还是操作数数量还是操作数上下限,代码如下:

 1 bool core::Setting(string option, int key)
 2 {//TODO ::to call the wrong parameter
 3     if (option == "calculate_kind")
 4     {
 5         config.calculate_kind = key;
 6     }
 7     if (option == "precision")
 8     {
 9         config.precision = key;
10         return true;
11     }
12     if (option == "ProblemNum")
13     {
14         config.ProblemNum = key;
15         return true;
16     }
17     if (option == "OperandNum")
18     {
19         config.OperandNum = key;
20         return true;
21     }
22     if (option == "lrange")
23     {
24         config.lrange = key;
25         return true;
26     }
27     if (option == "hrange")
28     {
29         config.hrange = key;
30         return true;
31     }
32     return false;
33 }
core.Setting()

b.core.Setting(char option,  bool key);

利用option来区分是什么运算符,key来确定是否加入,代码如下:

 1 bool core::Setting(char c, bool key)
 2 {//TODO ::to call the wrong parameter
 3     if (c == '+')
 4     {
 5         if(key)
 6             config.KindofOperator.append("+");
 7         return true;
 8     }
 9     if (c == '-')
10     {
11         if(key)
12             config.KindofOperator.append("-");
13         return true;
14     }
15     if (c == '*')
16     {
17         if(key)    
18             config.KindofOperator.append("*");
19         return true;
20     }
21     if (c == '/')
22     {
23         if(key)
24             config.KindofOperator.append("/");
25         return true;
26     }
27     if (c == '^')
28     {
29         if(key)
30             config.KindofOperator.append("^");
31         return true;
32     }
33 
34     return false;
35 }
core.Setting()

4.Generate方法

a.core.Generate()

  1 void core::Generate(string* &Problem, string* &result)
  2 {
  3     int test;
  4     vector<int> DivoperatorIndex;
  5     vector<int> PowoperatorIndex;
  6     int m;// for loop
  7     int overflow = 0;
  8     int calculate_kind = config.calculate_kind;
  9     if (calculate_kind == 0)
 10     {
 11         config.precision = 0;
 12     }
 13     int precision = config.precision;
 14     const int ProblemNum = config.ProblemNum;//the number of the problems
 15     const int OperandNum = config.OperandNum;//the number of the operands
 16     string KindofOperator = config.KindofOperator;//all of the kind of the operators
 17     if (KindofOperator.find("+") != string::npos)
 18         KindofOperator.append(30, '+');
 19     if (KindofOperator.find("-") != string::npos)
 20         KindofOperator.append(30, '-');
 21     if (KindofOperator.find("*") != string::npos)
 22         KindofOperator.append(10, '*');
 23     if (KindofOperator.find("/") != string::npos)
 24         KindofOperator.append(20, '/');
 25     if (KindofOperator.find("^") != string::npos)
 26         KindofOperator.append(8, '^');
 27     const int lrange = config.lrange; const int hrange = config.hrange;// the range of the operator and the result
 28     int* IntOperand;
 29     double *FloatOperand;//to store the operands, and to get their index
 30     IntOperand = new int[OperandNum];
 31     FloatOperand = new double[OperandNum];
 32     fraction *FractionOperand;
 33     FractionOperand = new fraction[OperandNum];
 34     char *Operator;// to store the operators, and to get their index
 35     Operator = new char[OperandNum];
 36 
 37     Problem = new string[ProblemNum];
 38     result = new string[ProblemNum];
 39     string OriginalProblem = "";
 40     int j;
 41     for (j = 0; j < ProblemNum && overflow < 10000*ProblemNum ; j++, overflow++)
 42     {
 43         PowoperatorIndex.clear();
 44         DivoperatorIndex.clear();
 45         
 46         OriginalProblem = "";
 47         for (int i = 0; i < OperandNum - 1; i++)
 48         {
 49             Operator[i] = KindofOperator[random(0, KindofOperator.size() - 1)];
 50             if (Operator[i] == '^')
 51                 PowoperatorIndex.push_back(i);
 52             if (Operator[i] == '/')
 53                 DivoperatorIndex.push_back(i);
 54         }
 55         if (calculate_kind == 0)
 56         {
 57             for (int i = 0; i < OperandNum; i++)
 58             {
 59                 IntOperand[i] = random(lrange, hrange);
 60             }
 61             for (int i = 0; i < PowoperatorIndex.size(); i++)
 62             {
 63                 IntOperand[PowoperatorIndex[i] + 1] = random(0, 3);
 64             }
 65             for (int i = DivoperatorIndex.size()-1; i >= 0 ; i--)
 66             {
 67                 IntOperand[DivoperatorIndex[i] + 1] = tofinddiv(IntOperand[DivoperatorIndex[i]]);
 68             }
 69 
 70             for (int i = 0; i < OperandNum - 1; i++)
 71             {
 72                 OriginalProblem += to_string(IntOperand[i]);
 73                 OriginalProblem += Operator[i];
 74             }
 75 
 76             OriginalProblem += to_string(IntOperand[OperandNum - 1]);
 77 
 78             
 79         }
 80         if (calculate_kind == 1)
 81         {
 82             for (int i = 0; i < OperandNum; i++)
 83             {
 84                 FloatOperand[i] = random((float)lrange, (float)hrange);
 85             }
 86             for (int i = 0; i < PowoperatorIndex.size(); i++)
 87             {
 88                 FloatOperand[PowoperatorIndex[i] + 1] = random(0, 3);
 89             }
 90             
 91             for (int i = 0; i < OperandNum - 1; i++)
 92             {
 93                 OriginalProblem += to_string_with_precision(FloatOperand[i], precision);
 94                 OriginalProblem += Operator[i];
 95             }
 96 
 97             OriginalProblem += to_string_with_precision(FloatOperand[OperandNum - 1], precision);
 98         }
 99         if (calculate_kind == 2)
100         {
101             for (int i = 0; i < OperandNum; i++)
102             {
103                 FractionOperand[i].denominator = random(lrange, hrange);
104                 FractionOperand[i].numerator = random(lrange, hrange);
105                 FractionOperand[i] = Simplify(FractionOperand[i]);
106             }
107             for (int i = 0; i < PowoperatorIndex.size(); i++)
108             {
109                 FractionOperand[PowoperatorIndex[i] + 1].numerator = random(0, 3);
110                 FractionOperand[PowoperatorIndex[i] + 1].denominator = 1;
111                 FractionOperand[PowoperatorIndex[i] + 1] = Simplify(FractionOperand[PowoperatorIndex[i] + 1]);
112             }
113             for (int i = 0; i < OperandNum - 1; i++)
114             {
115                 OriginalProblem += Frac2Str(FractionOperand[i]);
116                 OriginalProblem += Operator[i];
117             }
118             OriginalProblem += Frac2Str(FractionOperand[OperandNum - 1]);
119         }
120         Problem[j] = AddBracket(OriginalProblem);
121         cout << Problem[j] << endl;
122         if (Calc(Problem[j]) == "ERROR: expression!")
123         {
124             j--;
125             continue;
126         }
127         //cout << Problem[j] << endl;
128         for (m = 0; m < j; m++)
129         {
130             if (ProblemCompare(Problem[m], Problem[j]))//TODO
131             {
132                 break;
133             }
134         }
135         if (m != j)//
136         {
137             j--;
138             continue;
139         }//TODO::
140     }
141     if (overflow == 10000 * ProblemNum)
142     {
143         for (int i = j; i < ProblemNum; i++)
144             Problem[i] = "";
145     }
146 
147     for (int i = 0; i < ProblemNum; i++)
148     {
149         result[i] = Calc(Problem[i]);
150         //Problem[i] = to_unicode(Problem[i]);
151         //result[i] = to_unicode(result[i]);
152     }
153 }
154 v
Generate()

b.random()

由于程序需要大量使用随机数
编写了两个随机数的方法, 代码如下

 1 int core::random(int low, int high)
 2 {
 3 int i;
 4 srand(seed);
 5 i = (rand() % (high - low + 1)) + low;
 6 seed = (seed + 199) % RAND_MAX;
 7 return i;
 8 }
 9 double core::random(double low, double high)
10 {
11 int i = random((int)low*(int)low, (int)high*(int)high);
12 return sqrt(i);
13 }
random()

五、运行测试

 1.对接遇到的问题:

a.我们之前使用的是json的字典式传参,但是在对接时,发现json的第三方库始终无法成功地链接,我们遂将Setting改成上面的重载函数的参数式的传递

b.在UI组实验只选除号的整数运算时,我们因为设计中有整除的限制,导致随机生成的式子很难符号要求,代码需要跑很长时间,才能出结构于是我们遂在Generate里面就指定生成'/' 符号的后面的数字是前面数字的约数,这要大大提高了生成题目的速度

2.测试结果:

a.整数

b.小数

c.分数

六、项目相关

1.开发过程:

a.github管理代码:

b.psp表格:

 

c.代码规范

链接:http://www.cnblogs.com/chenzhikai/p/8633894.html

d.领航驾驶分工: 

Calc()模块:驾驶员-陈志锴  领航员-黄志鹏

setting模块:驾驶员-黄志鹏 领航员-陈志锴

generate模块:驾驶员-黄志鹏 领航员-陈志锴

2.代码细节:(一些领航员指出的驾驶员错误)

1.函数位置:所有调用的函数要放在class内,设置为private方法

2.变量命名:变量不能命名为简单的a,b,要修改为能表达含义的名称

3.分式与整式的判断:异常处理过程判断语句不能用str.find()的结果idx是否大于0来严重,而是要用string::npos来验证

4.函数定义:一开始写在cpp而不是.h里防止重复调用或调用错误,最后再整合

3.思考总结:

此次结对编程任务总耗时正常,安排凌乱,代码不多,重构多次。主要有以下几个因素:

a.项目开始时缺少统一规定。

b.core组与UI组沟通不及时。

c.各个组接口不同导致对接困难。

d.由于qtcreator和vs的兼容问题,放弃了json传参,大改了代码。

 API的重要性不言而喻,及时有效的交流非常关键!代码反复修改很正常,重构是为了使代码更加的完美。

原文地址:https://www.cnblogs.com/chenzhikai/p/8850106.html