【结对编程】四则运算生成器
结对项目报告
源码及接口说明地址: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;
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 }
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 }
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 }
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 }
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 }
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 }
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
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 }
五、运行测试
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的重要性不言而喻,及时有效的交流非常关键!代码反复修改很正常,重构是为了使代码更加的完美。