1. github地址:
2. PSP表格:
3. 效能分析:招队友
4. 设计实现过程:
①涉及到分数、整数和运算符的模拟运算,我们应该如何尽可能减少字符串的操作呢?本人受到ACM比赛中大数模板的启发,将分数、整数、运算符封装成一个类,创造出一种新的(假的)数据类型,配合上C++的重载运算符功能,重新定义符号运算,如此一来就可以直接使用+-*/对类对象进行与整数无异的四则运算。这样做还有一个好处,如果后续要添加运算符,如指数运算,直接在类里面添加operator character就可以,计算过程稍加修改就能用了,没有繁琐的字符串操作方便的很。这是数据结构派上用场的第一个地方。
②随机生成运算式子我认为是本次项目最繁琐的地方,我想了好久都想不出很好的简洁方便的写法,因为直接for一遍随机生成括号位置会出现一些完全无用的情况如(a ? b ? c),这样的括号是毫无意义的。另外这样做处理括号的嵌套就比较繁琐,如(a ? (b ? c)) ? d或者(( b ? c ) ? a) ? d,你说怎么rand才好? 考虑到运算符较少,我将所有情况在代码中列举了出来,对称的情况我就不管了比如(a ? b) ? c和a ? (b ? c),后者是前者的对称情况,如果要产生的式子看起来多样化一点只需要reverse一下数组,把括号方向改一下就能做到。但是枚举情况的话扩展性很差,毕竟划分数(贝尔数)的增长是很快的,随着运算式子长度加大情况增长会越来越快,届时没得办法只能乱rand。如有更好的办法欢迎留言。
③计算运算式子,很简单的啦。学过数据结构的都知道,用辅助栈就能将中缀表达式转化为后缀表达式,还能用栈计算后缀表达式的值。用二叉树也行,很秀但没必要,反正时间复杂度都差不多,栈很简单就能实现了,画一棵树多麻烦啊。不知道的请查阅《数据结构与算法分析 C语言描述》。数据结构第二次派上用场。
④如何使得产生的式子的运算路径不同?既然要求路径不同很自然就会想到树结构,我这里偷懒了直接用C++的map容器(map
⑤什么生成答案放文件里,对比文件那都是小儿科了,学学输入输出流就能搞定。
我错了,要我读入文本并计算其中的式子......又要搞字符串啊啊啊啊啊!躲不过啊躲不过。不过还好问题不大,扫一遍字符串,把题目序号去掉,把字符串数字转化为Frac再比较答案就可以了。此处项目已基本完工。
流程图如下:
5. 代码说明:
①数据类型Frac(整数内部也用分数表示,同时包括了符号,反正空间管够)
class Frac { public: Frac() { numerator = 0; denominator = 1; opChar = ""; //如果op为空,则表示这个不是运算符 } //以下重载+-*/运算符 Frac operator + (const Frac& x) { Frac frac; frac.denominator = this->denominator * x.denominator; frac.numerator = this->numerator * x.denominator + x.numerator * this->denominator; return frac; } Frac operator - (const Frac& x) { Frac frac; frac.denominator = this->denominator * x.denominator; frac.numerator = this->numerator * x.denominator - x.numerator * this->denominator; return frac; } Frac operator / (const Frac& x) { Frac frac; frac.denominator = this->denominator * x.numerator; frac.numerator = this->numerator * x.denominator; return frac; } Frac operator * (const Frac& x) { Frac frac; frac.denominator = this->denominator * x.denominator; frac.numerator = this->numerator * x.numerator; return frac; } //以下是随机生成的函数,或分数或整数或符号 void setFraction(int range) { this->denominator = rand() % range + 1; this->numerator = rand() % (range + 1); } void setInteger(int range) { this->numerator = rand() % (range + 1); this->denominator = 1; } void setNum(int range) { this->opChar = ""; rand() % 2 ? setFraction(range) : setInteger(range); } void setOpChar(int isBrac) { if (!isBrac) { int select = rand() % 4; switch (select) { case 0: this->opChar += "+"; break; case 1: this->opChar += "-"; break; case 2: this->opChar += "*"; break; case 3: this->opChar += "÷"; break; default: break; } } else { isBrac == 1 ? this->opChar += "(" : this->opChar += ")"; } } //输出函数,输出分数是最简形式,符号直接输出 void print() { if (this->opChar == "") { int GCD = gcd(this->denominator, this->numerator); this->denominator /= GCD, this->numerator /= GCD; if (this->denominator == 1) //整数 cout << this->numerator << ' '; else if (this->numerator > this->denominator) { //带分数 cout << this->numerator / this->denominator << '\'' << this->numerator % this->denominator << '/' << this->denominator << ' '; } else //真分数 cout << this->numerator << '/' << this->denominator << ' '; } else { cout << this->opChar << ' '; } } public: int numerator; int denominator; string opChar; };
②生成表达式:这个我不放了,太占地方,没什么难度,要注意的是带括号的表达式我是先生成括号再插入数字和运算符构造的,所以你将会看到一堆insert。
③生成/计算表达式/检测文件对答案:
class Ans :public Expression { private: vectorexpList; vector ans; map<string, bool> path; stack op, num; vector sufExp, beChecked; public: void init() { while (!op.empty()) op.pop(); while (!num.empty()) num.pop(); sufExp.clear(); beChecked.clear(); } //通过判断运算符优先级看是否出栈,其中'('要碰到')'才能出栈,而且不作为后缀表达式一部分,要特殊处理一下 bool isPop(string &nowOp, string &stackTopOp) { if (stackTopOp == "(") return false; else if ((nowOp == "*" || nowOp == "÷") && (stackTopOp == "*" || stackTopOp == "÷")) return true; else return (nowOp == "+" || nowOp == "-" || nowOp == ")") ? true : false; } //中缀表达式转化为后缀表达式,exp是中缀,op是运算符栈,sufExp是后缀表达式,具体算法过程参考数据结构书,在此不表 void transform(vector &exp, stack &op, vector &sufExp) { for (auto nowFrac : exp) { if (nowFrac.opChar == "") sufExp.push_back(nowFrac); else { if (op.empty()) op.push(nowFrac); else { while (!op.empty() && isPop(nowFrac.opChar, op.top().opChar)) { if (op.top().opChar != "(") sufExp.push_back(op.top()); op.pop(); } if (nowFrac.opChar == ")" && op.top().opChar == "(") op.pop(); if(nowFrac.opChar != ")") op.push(nowFrac); } } } while (!op.empty()) { sufExp.push_back(op.top()); op.pop(); } } //后缀表达式计算,num是数字栈 bool calExp(vector &sufExp, stack &num) { //cout << sufExp.size() << ' ' << num.size() << endl; Frac num1, num2, num3; string nowpath; for (auto nowFrac : sufExp) { //nowFrac.print(); if (nowFrac.opChar == "") num.push(nowFrac); else { num2 = num.top(); num.pop(); num1 = num.top(); num.pop(); if (nowFrac.opChar == "+") num3 = num1 + num2; else if (nowFrac.opChar == "-") num3 = num1 - num2; else if (nowFrac.opChar == "*") num3 = num1 * num2; else { if (num2.numerator == 0) num3.numerator = -1; else num3 = num1 / num2; } if (num3.numerator < 0) return false; num.push(num3); nowpath += to_string(num3.numerator) + to_string(num3.denominator); //构造运算路径 } } //如果nowpath已经在map中标记过,说明生成了效果一模一样的表达式,应当舍弃,重新生成 if (path[nowpath]) return false; path[nowpath] = true; //num3.print(); //cout << " haha " << endl; ans.push_back(num3); //cout << endl; return true; } //随机生成表达式的函数 void generate(int val, int range) { for (int i = 0; i < val; ++i) { Expression tmp; tmp.hardVersion(range); //stack op, num; //vectorsufExp; transform(tmp.exp, op, sufExp); if (calExp(sufExp, num)) expList.push_back(tmp); else i--; init(); } } //以下是输出函数 void printExp() { freopen("Exercises.txt", "w", stdout); for (int i = 0; i < expList.size(); ++i) { cout << i + 1 << ". "; expList[i].print(); cout << endl; } fclose(stdout); } void printAns() { freopen("Answers.txt", "w", stdout); for (int i = 0; i < ans.size(); ++i) { cout << i + 1 << ". "; ans[i].print(); cout << endl; } fclose(stdout); } void printAll() { for (int i = 0; i < ans.size(); ++i) { expList[i].print(); cout << " equals to "; ans[i].print(); cout << endl; } } //字符串转Frac Frac stringToFrac(string &number) { vector <int> part; string tmp = ""; Frac now; number += "$"; //方便处理数字而已,可以忽略,细节罢了 //string to int for (auto ch : number) { if (isalnum(ch)) tmp += ch; else { if (tmp != "") { part.push_back(stoi(tmp)); tmp = ""; } } } if (part.size() == 1) { //整数 now.numerator = part[0]; return now; } else if (part.size() == 2) { //分数 now.numerator = part[0], now.denominator = part[1]; return now; } else { //带分数 now.numerator = part[0] * part[2] + part[1], now.denominator = part[2]; return now; } } void readAndCheck(string &exercise, string &answer) { fstream exerciseFile; exerciseFile.open(exercise); //输入用的c++的流,输出用的c的freopen重定向,因为输入freopen只能重定向一次所以用的c++,输出多次重定向没问题 string buffer; while (!exerciseFile.eof()) { getline(exerciseFile, buffer); if (buffer.size() == 0) //空行跳过 continue; string number = ""; buffer += "$"; //加这个无意义符号是为了最后一个数能在循环内被处理掉,用空格也行的 //把序号去掉 for (int i = 0; i < buffer.length(); ++i) { if (buffer[i] == '.') { buffer.erase(0, i + 1); break; } } bool flagOfDiv = false; for (auto ch : buffer) { if (flagOfDiv) { flagOfDiv = false; continue; } //-95对应÷的第一个byte(-95 / -62),拓展ASC码(在string占两位),要特殊处理 if (ch == -95) { flagOfDiv = true; if (number != "") { beChecked.push_back(stringToFrac(number)); number = ""; } Frac tmp; tmp.opChar += "÷"; beChecked.push_back(tmp); continue; } if (isalnum(ch) || ch == '/' || ch == '\'') number += ch; else { if (number != "") { beChecked.push_back(stringToFrac(number)); number = ""; } if (ch != ' ' && ch != '$') { Frac tmp; tmp.opChar += ch; beChecked.push_back(tmp); } } } transform(beChecked, op, sufExp); calExp(sufExp, num); //答案将在ans的Frac容器里 init(); } exerciseFile.close(); //处理用户的答案文件,将答案转化为Frac类型再比较标准答案 fstream ansFile; ansFile.open(answer); vectoruserAns; vector<int> correct, wrong; while (!ansFile.eof()) { getline(ansFile, buffer); if (buffer.size() == 0) continue; for (int i = 0; i < buffer.length(); ++i) { if (buffer[i] == '.') { buffer.erase(0, i + 1); break; } } userAns.push_back(stringToFrac(buffer)); } ansFile.close(); //cout << ans.size() << ' ' << userAns.size() << endl; //对答案 Frac tmp; for (int i = 0; i < ans.size(); ++i) { ans[i].print(); userAns[i].print(); cout << endl; tmp = ans[i] - userAns[i]; if (tmp.numerator == 0) correct.push_back(i + 1); else wrong.push_back(i + 1); } //写入成绩 freopen("Grade.txt", "w", stdout); cout << "Correct: " << correct.size() << " ( "; for (int i = 0; i < correct.size(); ++i) { cout << correct[i]; if(i != correct.size() - 1) cout << ","; } cout << " ) \n"; cout << "Wrong: " << wrong.size() << " ( "; for (int i = 0; i < wrong.size(); ++i) { cout << wrong[i]; if(i != wrong.size() - 1) cout << ","; } cout << " ) \n"; fclose(stdout); } };
6. 测试运行:广告位招租,招队友