# 四则运算生成器 **Repository Path**: linxi99/four_operational_generators ## Basic Information - **Project Name**: 四则运算生成器 - **Description**: No description available - **Primary Language**: C++ - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2019-03-11 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 1 任务描述 ## 1.1 概述 使用java或C/C++编程语言,独立完成一个3到5个运算符的四则运算练习的软件 ## 1.2 基本要求 - 1 程序可接收一个输入参数n,然后随机产生n道加减乘除(分别使用符号+-*÷来表示)练习题,每个数字在 0 和 100 之间,运算符在3个到5个之间。 - 2 每个练习题至少要包含2种运算符。同时,由于小学生没有分数与负数的概念,你所出的练习题在运算过程中不得出现负数与非整数,比如不能出 3÷5+2=2.6,2-5+10=7等算式。 - 3 练习题生成好后,将你的学号与生成的n道练习题及其对应的正确答案输出到文件“result.txt”中,不要输出额外信息,文件目录与程序目录一致。 - 4 当程序接收的参数为4时,以下为一个输出文件示例。 2018010203 13+17-1=29 11*15-5=160 3+10+4-16=1 15÷5+3-2=4 ## 1.3 附加功能要求 - 1 支持有括号的运算式,包括出题与求解正确答案。注意,算式中存在的括号数必须大于2对,且不得超过运算符的个数。 - 2 扩展程序功能支持真分数的出题与运算(只需要涵盖加减法即可),例如:1/6 + 1/8 + 2/3= 23/24。注意在实现本功能时,需支持运算时分数的自动化简,比如 1/2+1/6=2/3,而非4/6,且计算过程中与结果都须为真分数。 # 2 项目地址 个人博客 https://linxi99.gitee.io/ https://linxi99.gitee.io/20190319/Arithmetic-Device/ https://blog.csdn.net/linxilinxilinxi https://blog.csdn.net/linxilinxilinxi/article/details/88548481 项目地址 https://gitee.com/linxi99/four_operational_generators # 3 项目源代码 ```cpp #include using namespace std; mt19937 mt(time(0)); map op; map pri; struct node{ int id, val; node(int id = -1, int val = -1):id(id), val(val){} }; int tot, operatorNum, operandNum, bracketNum, ans; int hasBracket[15], operators[15], operands[15]; int RPN[55], fac[105]; stack opr; stack opd; void init1(){ tot = 0; for(int i = 0; i < 15; ++i){ operators[i] = -1; operands[i] = -1; hasBracket[i] = -1; } while(!opr.empty()) opr.pop(); while(!opd.empty()) opd.pop(); } void init2(){ tot = 0; while(!opr.empty()) opr.pop(); while(!opd.empty()) opd.pop(); } bool getOperands(){ init2(); if(hasBracket[0] != -1) opr.push(hasBracket[0]); RPN[tot++] = 0; // puts("**3.1**"); for(int i = 1; i < operandNum; ++i){ // printf("%d\n", i); while(true){ if(opr.empty() || opr.top() == 14 || pri[operators[i - 1]] > pri[opr.top()]){ opr.push(operators[i - 1]); break; } RPN[tot++] = opr.top(); opr.pop(); } if(hasBracket[i] == 14){ opr.push(hasBracket[i]); } RPN[tot++] = i; if(hasBracket[i] == 15){ while(opr.top() != 14){ RPN[tot++] = opr.top(); opr.pop(); } opr.pop(); } } // puts("**3.2**"); while(!opr.empty()){ RPN[tot++] = opr.top(); opr.pop(); } // for(int i = 0; i < tot; ++i){ // printf("%d ", RPN[i]); // } // puts(""); // puts("**3.3**"); for(int i = 0; i < tot; ++i){ if(RPN[i] < 10){ int x = mt()%66 + 1; operands[RPN[i]] = x; opd.push(node(RPN[i], x)); continue; } if(RPN[i] == 13){ node b = opd.top(); opd.pop(); node a = opd.top(); opd.pop(); if(a.val%b.val == 0){ opd.push(node(-1, a.val/b.val)); continue; } if(b.id == -1) return false; int cnt = 0; for(int j = 1; j <= a.val; ++j){ if(a.val%j) continue; fac[cnt++] = j; } int x = mt()%cnt; operands[b.id] = fac[x]; opd.push(node(-1, a.val/fac[x])); }else if(RPN[i] == 11){ node b = opd.top(); opd.pop(); node a = opd.top(); opd.pop(); int dt = a.val - b.val; if(dt <= 0) return false; opd.push(node(-1, dt)); }else{ node b = opd.top(); opd.pop(); node a = opd.top(); opd.pop(); if(RPN[i] == 10) opd.push(node(-1, a.val + b.val)); if(RPN[i] == 12) opd.push(node(-1, a.val*b.val)); } } // puts("**3.4**"); ans = opd.top().val; opd.pop(); if(ans < 0 || ans > 1000) return false; // for(int i = 0; i < tot; ++i){ // printf("%d ", RPN[i]); // } // puts(""); return true; } bool solve(){ init1(); operatorNum = mt()%3 + 3; operandNum = operatorNum + 1; bracketNum = min((int)(operandNum/2), (int)(mt()%2 + 2)); // printf("%d %d %d\n", operatorNum, operandNum, bracketNum); for(int i = 0; i < operatorNum; ++i) operators[i] = mt()%4 + 10; for(int i = 0; i < bracketNum*2; ++i){ int x = mt()%operandNum; while(hasBracket[x] != -1) x = mt()%operandNum; hasBracket[x] = 0; } // puts("**1**"); bool lf = true; for(int i = 1; i < operandNum; ++i){ if(operators[i - 1] == 13){ if(hasBracket[i] == 14){ for(int j = 0; j < 15; ++j) hasBracket[j] = -1; break; } if(i < operandNum - 1 && operators[i] >= 12) return false; } } // puts("**2**"); for(int i = 0; i < operandNum; ++i){ if(hasBracket[i] == -1) continue; if(lf) hasBracket[i] = 14; else hasBracket[i] = 15; lf = (!lf); } // puts("**3**"); if(!getOperands()) return false; // puts("**4**"); if(hasBracket[0] != -1) printf("("); printf("%d", operands[0]); for(int i = 1; i < operandNum; ++i){ if(operators[i - 1] == 13) printf(""); else printf("%c", op[operators[i - 1]]); if(hasBracket[i] == 14) printf("("); printf("%d", operands[i]); if(hasBracket[i] == 15) printf(")"); } printf("=%d\n", ans); return true; } int main(){ op[10] = '+', op[11] = '-'; op[12] = '*'; op[14] = '(', op[15] = ')'; pri[10] = 0, pri[11] = 0; pri[12] = 1, pri[13] = 1; pri[14] = 2, pri[15] = 2; freopen("../result.txt","w",stdout); int n; scanf("%d", &n); puts("2017012449"); while(n--) while(!solve()); return 0; } ``` # 4 更新版本 ## 4.1 源代码 1.1 ```cpp #include using namespace std; mt19937 mt(time(0)); // 随机数生成器 map op; // 运算符id及其符号映射 map pri; // 运算符id及其优先级映射 // 10表示加号 运算优先级为 0 // 11表示减号 运算优先级为 0 // 12表示乘号 运算优先级为 1 // 13表示除号 运算优先级为 1 // 14表示左括号 运算优先级为 2 // 15表示右括号 运算优先级为 2 // 运算数结构体,id为运算数的位置,val为运算数的值 struct node{ int id, val; node(int id = -1, int val = -1):id(id), val(val){} }; // tot:逆波兰表达式的长度,operatorNum:运算符个数 // operandNum:运算数的个数,bracketNum:括号对数,ans:运算式最终答案 // hasBracket:某个数字处是否有括号,-1表示无括号,14表示左括号,15表示右括号 // operators:对应位置的运算符种类 operands:表示对应位置运算数的值 // RPN:存储逆波兰表达式 fac:存储某个数的约数 // opr:中缀表达式转后缀表达式时的运算符栈 // opd:中缀表达式转后缀表达式时的运算数栈 int tot, operatorNum, operandNum, bracketNum, ans; int hasBracket[15], operators[15], operands[15]; int RPN[55], fac[105]; stack opr; stack opd; // 初始化函数零:初始化运算符 id 符号 以及运算优先级,并且重定向输出 void init0(){ op[10] = '+', op[11] = '-'; op[12] = '*'; op[14] = '(', op[15] = ')'; pri[10] = 0, pri[11] = 0; pri[12] = 1, pri[13] = 1; pri[14] = 2, pri[15] = 2; freopen("../result.txt", "w", stdout); } // 初始化函数一:在每次调用solve()时进行初始化 void init1(){ tot = 0; for(int i = 0; i < 15; ++i){ operators[i] = -1; operands[i] = -1; hasBracket[i] = -1; } while(!opr.empty()) opr.pop(); while(!opd.empty()) opd.pop(); } // 初始化函数二:在每次调用getOperands()时进行初始化 void init2(){ tot = 0; while(!opr.empty()) opr.pop(); while(!opd.empty()) opd.pop(); } // 获取运算数的值的函数,如果返回 true 则表示获取成功,否则表示获取失败 bool getOperands(){ init2(); // 将中缀表达式转换成后缀表达式(也就是逆波兰表达式) if(hasBracket[0] != -1) opr.push(hasBracket[0]); RPN[tot++] = 0; for(int i = 1; i < operandNum; ++i){ while(true){ if(opr.empty() || opr.top() == 14 || pri[operators[i - 1]] > pri[opr.top()]){ opr.push(operators[i - 1]); break; } RPN[tot++] = opr.top(); opr.pop(); } if(hasBracket[i] == 14){ opr.push(hasBracket[i]); } RPN[tot++] = i; if(hasBracket[i] == 15){ while(opr.top() != 14){ RPN[tot++] = opr.top(); opr.pop(); } opr.pop(); } } while(!opr.empty()){ RPN[tot++] = opr.top(); opr.pop(); } // 转换成逆波兰表达式后便可以进行尝试填数 for(int i = 0; i < tot; ++i){ // 如果为运算数则随机为其赋值 if(RPN[i] < 10){ int x = mt()%66 + 1; operands[RPN[i]] = x; opd.push(node(RPN[i], x)); continue; } //如果为除法,要将除数随机分配为被除数的一个因子 //如果为减法,要注意减数不能大于被减数 if(RPN[i] == 13){ node b = opd.top(); opd.pop(); node a = opd.top(); opd.pop(); if(a.val%b.val == 0){ opd.push(node(-1, a.val/b.val)); continue; } if(b.id == -1) return false; int cnt = 0; for(int j = 1; j <= a.val; ++j){ if(a.val%j) continue; fac[cnt++] = j; } int x = mt()%cnt; operands[b.id] = fac[x]; opd.push(node(-1, a.val/fac[x])); }else if(RPN[i] == 11){ node b = opd.top(); opd.pop(); node a = opd.top(); opd.pop(); int dt = a.val - b.val; if(dt <= 0) return false; opd.push(node(-1, dt)); }else{ node b = opd.top(); opd.pop(); node a = opd.top(); opd.pop(); if(RPN[i] == 10) opd.push(node(-1, a.val + b.val)); if(RPN[i] == 12) opd.push(node(-1, a.val*b.val)); } } ans = opd.top().val; opd.pop(); //控制最终运算结果的范围,可根据需要进行调节 if(ans < 0 || ans > 1000) return false; return true; } bool solve(){ init1(); // 随机生成运算符的个数 3~5 ,及运算数个数 4~6 operatorNum = mt()%3 + 3; operandNum = operatorNum + 1; // 随机生成括号个数 bracketNum = min((int)(operandNum/2), (int)(mt()%3 + 2)); // 随机生成运算符的种类 for(int i = 0; i < operatorNum; ++i) operators[i] = mt()%4 + 10; // 随机生成括号位置 for(int i = 0; i < bracketNum*2; ++i){ int x = mt()%operandNum; while(hasBracket[x] != -1) x = mt()%operandNum; hasBracket[x] = 0; } // 根据相对位置确定括号为左括号还是右括号 bool lf = true; for(int i = 0; i < operandNum; ++i){ if(hasBracket[i] == -1) continue; if(lf) hasBracket[i] = 14; else hasBracket[i] = 15; lf = (!lf); } // 到这里已经将等式预处理成了(a+b)/c*(d-e)的类似形式 // 预处理结束之后,我们就要尝试将 a b c d e 确定为具体的数 if(!getOperands()) return false; // 在获取完运算数之后,便可以输出我们得到的等式了 if(hasBracket[0] != -1) printf("("); printf("%d", operands[0]); for(int i = 1; i < operandNum; ++i){ if(operators[i - 1] == 13) printf("÷"); else printf("%c", op[operators[i - 1]]); if(hasBracket[i] == 14) printf("("); printf("%d", operands[i]); if(hasBracket[i] == 15) printf(")"); } printf("=%d\n", ans); return true; } int main(){ // 初始化函数零:初始化运算符 id 符号 以及运算优先级,并且重定向输出 init0(); //读入需要生成的运算式数量 int n; scanf("%d", &n); //输出学号及运算式 puts("2017012449"); while(n--) while(!solve()) ; return 0; } ``` ## 4.2 源代码 1.2 ```cpp #include using namespace std; mt19937 mt(time(0)); // 随机数生成器 map op; // 运算符id及其符号映射 map pri; // 运算符id及其优先级映射 // 10表示加号 运算优先级为 0 // 11表示减号 运算优先级为 0 // 12表示乘号 运算优先级为 1 // 13表示除号 运算优先级为 1 // 14表示左括号 运算优先级为 2 // 15表示右括号 运算优先级为 2 // 运算数结构体,id为运算数的位置,val为运算数的值 struct node{ int id, val; node(int id = -1, int val = -1):id(id), val(val){} }; // tot:逆波兰表达式的长度,operatorNum:运算符个数 // operandNum:运算数的个数,bracketNum:括号对数,ans:运算式最终答案 // hasBracket:某个数字处是否有括号,-1表示无括号,14表示左括号,15表示右括号 // operators:对应位置的运算符种类 operands:表示对应位置运算数的值 // RPN:存储逆波兰表达式 fac:存储某个数的约数 // opr:中缀表达式转后缀表达式时的运算符栈 // opd:中缀表达式转后缀表达式时的运算数栈 int tot, operatorNum, operandNum, bracketNum, ans; int hasBracket[15], operators[15], operands[15]; int RPN[55], fac[105]; stack opr; stack opd; // 初始化函数零:初始化运算符 id 符号 以及运算优先级,并且重定向输出 void init0(){ op[10] = '+', op[11] = '-'; op[12] = '*'; op[14] = '(', op[15] = ')'; pri[10] = 0, pri[11] = 0; pri[12] = 1, pri[13] = 1; pri[14] = 2, pri[15] = 2; freopen("../result.txt", "w", stdout); } // 初始化函数一:在每次调用solve()时进行初始化 void init1(){ tot = 0; for(int i = 0; i < 15; ++i){ operators[i] = -1; operands[i] = -1; hasBracket[i] = -1; } while(!opr.empty()) opr.pop(); while(!opd.empty()) opd.pop(); } // 初始化函数二:在每次调用getOperands()时进行初始化 void init2(){ tot = 0; while(!opr.empty()) opr.pop(); while(!opd.empty()) opd.pop(); } // 获取运算数的值的函数,如果返回 true 则表示获取成功,否则表示获取失败 bool getOperands(){ init2(); // 将中缀表达式转换成后缀表达式(也就是逆波兰表达式) if(hasBracket[0] != -1) opr.push(hasBracket[0]); RPN[tot++] = 0; for(int i = 1; i < operandNum; ++i){ while(true){ if(opr.empty() || opr.top() == 14 || pri[operators[i - 1]] > pri[opr.top()]){ opr.push(operators[i - 1]); break; } RPN[tot++] = opr.top(); opr.pop(); } if(hasBracket[i] == 14){ opr.push(hasBracket[i]); } RPN[tot++] = i; if(hasBracket[i] == 15){ while(opr.top() != 14){ RPN[tot++] = opr.top(); opr.pop(); } opr.pop(); } } while(!opr.empty()){ RPN[tot++] = opr.top(); opr.pop(); } // 转换成逆波兰表达式后便可以进行尝试填数 for(int i = 0; i < tot; ++i){ // 如果为运算数则随机为其赋值 if(RPN[i] < 10){ int x = mt()%66 + 1; operands[RPN[i]] = x; opd.push(node(RPN[i], x)); continue; } //如果为除法,要将除数随机分配为被除数的一个因子 //如果为减法,要注意减数不能大于被减数 if(RPN[i] == 13){ node b = opd.top(); opd.pop(); node a = opd.top(); opd.pop(); if(a.val%b.val == 0){ opd.push(node(-1, a.val/b.val)); continue; } if(b.id == -1) return false; int cnt = 0; for(int j = 1; j <= a.val; ++j){ if(j >= 100) break; if(a.val%j) continue; fac[cnt++] = j; } int x = mt()%cnt; operands[b.id] = fac[x]; opd.push(node(-1, a.val/fac[x])); }else if(RPN[i] == 11){ node b = opd.top(); opd.pop(); node a = opd.top(); opd.pop(); int dt = a.val - b.val; if(dt <= 0) return false; opd.push(node(-1, dt)); }else{ node b = opd.top(); opd.pop(); node a = opd.top(); opd.pop(); if(RPN[i] == 10) opd.push(node(-1, a.val + b.val)); if(RPN[i] == 12) opd.push(node(-1, a.val*b.val)); } } ans = opd.top().val; opd.pop(); //控制最终运算结果的范围,可根据需要进行调节 if(ans < 0 || ans > 1000) return false; return true; } bool solve(){ init1(); // 随机生成运算符的个数 3~5 ,及运算数个数 4~6 operatorNum = mt()%3 + 3; operandNum = operatorNum + 1; // 随机生成括号个数 bracketNum = min((int)(operandNum/2), (int)(mt()%3 + 2)); // 随机生成运算符的种类 for(int i = 0; i < operatorNum; ++i) operators[i] = mt()%4 + 10; // 随机生成括号位置 for(int i = 0; i < bracketNum*2; ++i){ int x = mt()%operandNum; while(hasBracket[x] != -1) x = mt()%operandNum; hasBracket[x] = 0; } // 根据相对位置确定括号为左括号还是右括号 bool lf = true; for(int i = 0; i < operandNum; ++i){ if(hasBracket[i] == -1) continue; if(lf) hasBracket[i] = 14; else hasBracket[i] = 15; lf = (!lf); } // 到这里已经将等式预处理成了(a+b)/c*(d-e)的类似形式 // 预处理结束之后,我们就要尝试将 a b c d e 确定为具体的数 if(!getOperands()) return false; // 在获取完运算数之后,便可以输出我们得到的等式了 if(hasBracket[0] != -1) printf("("); printf("%d", operands[0]); for(int i = 1; i < operandNum; ++i){ if(operators[i - 1] == 13) printf("÷"); else printf("%c", op[operators[i - 1]]); if(hasBracket[i] == 14) printf("("); printf("%d", operands[i]); if(hasBracket[i] == 15) printf(")"); } printf("=%d\n", ans); return true; } int main(){ // 初始化函数零:初始化运算符 id 符号 以及运算优先级,并且重定向输出 init0(); //读入需要生成的运算式数量 int n; scanf("%d", &n); //输出学号及运算式 puts("2017012449"); while(n--) while(!solve()) ; return 0; } ``` ## 4.3 源代码 1.3 ```cpp #include using namespace std; mt19937 mt(time(0)); // 随机数生成器 map op; // 运算符id及其符号映射 map pri; // 运算符id及其优先级映射 // 10表示加号 运算优先级为 0 // 11表示减号 运算优先级为 0 // 12表示乘号 运算优先级为 1 // 13表示除号 运算优先级为 1 // 14表示左括号 运算优先级为 2 // 15表示右括号 运算优先级为 2 // 运算数结构体,id为运算数的位置,val为运算数的值 struct node{ int id, val; node(int id = -1, int val = -1):id(id), val(val){} }; // tot:逆波兰表达式的长度,operatorNum:运算符个数 // operandNum:运算数的个数,bracketNum:括号对数,ans:运算式最终答案 // hasBracket:某个数字处是否有括号,-1表示无括号,14表示左括号,15表示右括号 // operators:对应位置的运算符种类 operands:表示对应位置运算数的值 // RPN:存储逆波兰表达式 fac:存储某个数的约数 // opr:中缀表达式转后缀表达式时的运算符栈 // opd:中缀表达式转后缀表达式时的运算数栈 // numerators:对应位置运算数的分子 denominators:对应位置运算式的分母 int tot, operatorNum, operandNum, bracketNum, ans; int hasBracket[15], operators[15], operands[15]; int RPN[55], fac[105], numerators[15], denominators[15]; stack opr; stack opd; // 初始化函数零:初始化运算符 id 符号 以及运算优先级,并且重定向输出 void init0(){ op[10] = '+', op[11] = '-'; op[12] = '*'; op[14] = '(', op[15] = ')'; pri[10] = 0, pri[11] = 0; pri[12] = 1, pri[13] = 1; pri[14] = 2, pri[15] = 2; freopen("../result.txt", "w", stdout); } // 初始化函数一:在每次调用solve()时进行初始化 void init1(){ tot = 0; for(int i = 0; i < 15; ++i){ operators[i] = -1; operands[i] = -1; hasBracket[i] = -1; } while(!opr.empty()) opr.pop(); while(!opd.empty()) opd.pop(); } // 初始化函数二:在每次调用getOperands()时进行初始化 void init2(){ tot = 0; while(!opr.empty()) opr.pop(); while(!opd.empty()) opd.pop(); } // 初始化函数三:在每次调用solve2()时进行初始化 void init3(){ tot = 0; for(int i = 0; i < 15; ++i){ operators[i] = -1; numerators[i] = -1; denominators[i] = -1; } } // 获取运算数的值的函数,如果返回 true 则表示获取成功,否则表示获取失败 bool getOperands(){ init2(); // 将中缀表达式转换成后缀表达式(也就是逆波兰表达式) if(hasBracket[0] != -1) opr.push(hasBracket[0]); RPN[tot++] = 0; for(int i = 1; i < operandNum; ++i){ while(true){ if(opr.empty() || opr.top() == 14 || pri[operators[i - 1]] > pri[opr.top()]){ opr.push(operators[i - 1]); break; } RPN[tot++] = opr.top(); opr.pop(); } if(hasBracket[i] == 14){ opr.push(hasBracket[i]); } RPN[tot++] = i; if(hasBracket[i] == 15){ while(opr.top() != 14){ RPN[tot++] = opr.top(); opr.pop(); } opr.pop(); } } while(!opr.empty()){ RPN[tot++] = opr.top(); opr.pop(); } // 转换成逆波兰表达式后便可以进行尝试填数 for(int i = 0; i < tot; ++i){ // 如果为运算数则随机为其赋值 if(RPN[i] < 10){ int x = mt()%66 + 1; operands[RPN[i]] = x; opd.push(node(RPN[i], x)); continue; } //如果为除法,要将除数随机分配为被除数的一个因子 //如果为减法,要注意减数不能大于被减数 if(RPN[i] == 13){ node b = opd.top(); opd.pop(); node a = opd.top(); opd.pop(); if(a.val%b.val == 0){ opd.push(node(-1, a.val/b.val)); continue; } if(b.id == -1) return false; int cnt = 0; for(int j = 1; j <= a.val; ++j){ if(j >= 100) break; if(a.val%j) continue; fac[cnt++] = j; } int x = mt()%cnt; operands[b.id] = fac[x]; opd.push(node(-1, a.val/fac[x])); }else if(RPN[i] == 11){ node b = opd.top(); opd.pop(); node a = opd.top(); opd.pop(); int dt = a.val - b.val; if(dt <= 0) return false; opd.push(node(-1, dt)); }else{ node b = opd.top(); opd.pop(); node a = opd.top(); opd.pop(); if(RPN[i] == 10) opd.push(node(-1, a.val + b.val)); if(RPN[i] == 12) opd.push(node(-1, a.val*b.val)); } } ans = opd.top().val; opd.pop(); //控制最终运算结果的范围,可根据需要进行调节 if(ans < 0 || ans > 1000) return false; return true; } // solve1() 表示生成普通运算式 bool solve1(){ init1(); // 随机生成运算符的个数 3~5 ,及运算数个数 4~6 operatorNum = mt()%3 + 3; operandNum = operatorNum + 1; // 随机生成括号个数 bracketNum = min((int)(operandNum/2), (int)(mt()%3 + 2)); // 随机生成运算符的种类 for(int i = 0; i < operatorNum; ++i) operators[i] = mt()%4 + 10; // 随机生成括号位置 for(int i = 0; i < bracketNum*2; ++i){ int x = mt()%operandNum; while(hasBracket[x] != -1) x = mt()%operandNum; hasBracket[x] = 0; } // 根据相对位置确定括号为左括号还是右括号 bool lf = true; for(int i = 0; i < operandNum; ++i){ if(hasBracket[i] == -1) continue; if(lf) hasBracket[i] = 14; else hasBracket[i] = 15; lf = (!lf); } // 到这里已经将等式预处理成了(a+b)/c*(d-e)的类似形式 // 预处理结束之后,我们就要尝试将 a b c d e 确定为具体的数 if(!getOperands()) return false; // 在获取完运算数之后,便可以输出我们得到的等式了 if(hasBracket[0] != -1) printf("("); printf("%d", operands[0]); for(int i = 1; i < operandNum; ++i){ if(operators[i - 1] == 13) printf("÷"); else printf("%c", op[operators[i - 1]]); if(hasBracket[i] == 14) printf("("); printf("%d", operands[i]); if(hasBracket[i] == 15) printf(")"); } printf("=%d\n", ans); return true; } // solve2() 生成真分数运算式 // 由于只要求加减法,因此括号出现与否并不会影响答案 // 故solve2()中无须考虑括号与乘除号 bool solve2(){ init3(); operatorNum = mt()%3 + 3; operandNum = operatorNum + 1; for(int i = 0; i < operatorNum; ++i) operators[i] = mt()%2 + 10; // 生成分子与分母,保证分子严格小于分母 denominators[0] = mt()%66 + 1; numerators[0] = mt()%(max(denominators[0] - 22, 1)) + 1; int g = __gcd(numerators[0], denominators[0]); numerators[0] /= g, denominators[0] /= g; int nowNume = numerators[0], nowDeno = denominators[0]; for(int i = 1; i < operandNum; ++i){ denominators[i] = mt()%66 + 1; numerators[i] = mt()%(max(denominators[i] - 22, 1)) + 1; g = __gcd(numerators[i], denominators[i]); numerators[i] /= g, denominators[i] /= g; g = __gcd(denominators[i], nowDeno); int lcm = nowDeno*denominators[i]/g; nowNume *= denominators[i]/g; if(operators[i - 1] == 10){ nowNume += numerators[i]*nowDeno/g; if(nowNume >= nowDeno) return false; }else if(operators[i - 1] == 11){ nowNume -= numerators[i]*nowDeno/g; if(nowNume <= 0) return false; } nowDeno = lcm; g = __gcd(nowNume, nowDeno); nowNume /= g, nowDeno /= g; // 当运算过程中分子分母大于阈值(这里是 666 ),则重新生成算式 if(nowNume > 666 || nowDeno > 666) return false; } printf("%d/%d", numerators[0], denominators[0]); for(int i = 1; i < operandNum; ++i){ printf("%c", op[operators[i - 1]]); printf("%d/%d", numerators[i], denominators[i]); } printf("=%d/%d\n", nowNume, nowDeno); return true; } // 这里 x 与模数控制分数运算式出现的概率 // 这里限定为 30% 的分数运算式, 70% 的普通运算式 void solve(){ int x = mt()%10; if(x <= 2) while(!solve2()) ; else while(!solve1()) ; } int main(){ // 初始化函数零:初始化运算符 id 符号 以及运算优先级,并且重定向输出 init0(); //读入需要生成的运算式数量 int n; scanf("%d", &n); //输出学号及运算式 puts("2017012449"); while(n--) solve(); return 0; } ``` ## 4.4 代码 1.4 ```cpp #include using namespace std; mt19937 mt(time(0)); // 随机数生成器 map op; // 运算符id及其符号映射 map pri; // 运算符id及其优先级映射 // 10表示加号 运算优先级为 0 // 11表示减号 运算优先级为 0 // 12表示乘号 运算优先级为 1 // 13表示除号 运算优先级为 1 // 14表示左括号 // 15表示右括号 // 运算数结构体,id为运算数的位置,val为运算数的值 struct node{ int id, val; node(int id = -1, int val = -1):id(id), val(val){} }; // tot:逆波兰表达式的长度,operatorNum:运算符个数 // operandNum:运算数的个数,bracketNum:括号对数,ans:运算式最终答案 // hasBracket:某个数字处是否有括号,-1表示无括号,14表示左括号,15表示右括号 // operators:对应位置的运算符种类 operands:表示对应位置运算数的值 // RPN:存储逆波兰表达式 fac:存储某个数的约数 // opr:中缀表达式转后缀表达式时的运算符栈 // opd:中缀表达式转后缀表达式时的运算数栈 // numerators:对应位置运算数的分子 denominators:对应位置运算式的分母 int tot, operatorNum, operandNum, bracketNum, ans; int hasBracket[15], operators[15], operands[15]; int RPN[55], fac[105], numerators[15], denominators[15]; stack opr; stack opd; // 初始化函数零:初始化运算符 id 符号 以及运算优先级,并且重定向输出 void init0(){ op[10] = '+', op[11] = '-'; op[12] = '*'; op[14] = '(', op[15] = ')'; pri[10] = 0, pri[11] = 0; pri[12] = 1, pri[13] = 1; freopen("../result.txt", "w", stdout); } // 初始化函数一:在每次调用solve()时进行初始化 void init1(){ tot = 0; for(int i = 0; i < 15; ++i){ operators[i] = -1; operands[i] = -1; hasBracket[i] = -1; } while(!opr.empty()) opr.pop(); while(!opd.empty()) opd.pop(); } // 初始化函数二:在每次调用getOperands()时进行初始化 void init2(){ tot = 0; while(!opr.empty()) opr.pop(); while(!opd.empty()) opd.pop(); } // 初始化函数三:在每次调用solve2()时进行初始化 void init3(){ tot = 0; for(int i = 0; i < 15; ++i){ operators[i] = -1; numerators[i] = -1; denominators[i] = -1; } } // 生成一个范围在 [l, r] 中的随机数 int getNum(int l, int r){ int ret = mt()%(r - l + 1) + l; return ret; } // 获取运算数的值的函数,如果返回 true 则表示获取成功,否则表示获取失败 bool getOperands(){ init2(); // 将中缀表达式转换成后缀表达式(也就是逆波兰表达式) if(hasBracket[0] != -1) opr.push(hasBracket[0]); RPN[tot++] = 0; for(int i = 1; i < operandNum; ++i){ while(true){ if(opr.empty() || opr.top() == 14 || pri[operators[i - 1]] > pri[opr.top()]){ opr.push(operators[i - 1]); break; } RPN[tot++] = opr.top(); opr.pop(); } if(hasBracket[i] == 14){ opr.push(hasBracket[i]); } RPN[tot++] = i; if(hasBracket[i] == 15){ while(opr.top() != 14){ RPN[tot++] = opr.top(); opr.pop(); } opr.pop(); } } while(!opr.empty()){ RPN[tot++] = opr.top(); opr.pop(); } // 转换成逆波兰表达式后便可以进行尝试填数 for(int i = 0; i < tot; ++i){ // 如果为运算数则随机为其赋值 if(RPN[i] < 10){ int x = getNum(1, 66); operands[RPN[i]] = x; opd.push(node(RPN[i], x)); continue; } //如果为除法,要将除数随机分配为被除数的一个因子 //如果为减法,要注意减数不能大于被减数 if(RPN[i] == 13){ node b = opd.top(); opd.pop(); node a = opd.top(); opd.pop(); if(a.val%b.val == 0){ opd.push(node(-1, a.val/b.val)); continue; } if(b.id == -1) return false; int cnt = 0; for(int j = 1; j <= a.val; ++j){ if(j >= 100) break; if(a.val%j) continue; fac[cnt++] = j; } int x =getNum(0, cnt - 1); operands[b.id] = fac[x]; opd.push(node(-1, a.val/fac[x])); }else if(RPN[i] == 11){ node b = opd.top(); opd.pop(); node a = opd.top(); opd.pop(); int dt = a.val - b.val; if(dt <= 0) return false; opd.push(node(-1, dt)); }else{ node b = opd.top(); opd.pop(); node a = opd.top(); opd.pop(); if(RPN[i] == 10) opd.push(node(-1, a.val + b.val)); if(RPN[i] == 12) opd.push(node(-1, a.val*b.val)); } } ans = opd.top().val; opd.pop(); //控制最终运算结果的范围,可根据需要进行调节 if(ans < 0 || ans > 1000) return false; return true; } // solve1() 表示生成普通运算式 bool solve1(bool flag){ init1(); // 随机生成运算符的个数 3~5 ,及运算数个数 4~6 operatorNum = getNum(3, 5); operandNum = operatorNum + 1; // 随机生成括号个数 bracketNum = min((int)(operandNum/2), getNum(2, 4)); if(flag) bracketNum = 0; // 随机生成运算符的种类 for(int i = 0; i < operatorNum; ++i) operators[i] = getNum(10, 13); // 随机生成括号位置 for(int i = 0; i < bracketNum*2; ++i){ int x = getNum(0, operandNum - 1); while(hasBracket[x] != -1) x = getNum(0, operandNum - 1); hasBracket[x] = 0; } // 根据相对位置确定括号为左括号还是右括号 bool lf = true; for(int i = 0; i < operandNum; ++i){ if(hasBracket[i] == -1) continue; if(lf) hasBracket[i] = 14; else hasBracket[i] = 15; lf = (!lf); } // 到这里已经将等式预处理成了(a+b)/c*(d-e)的类似形式 // 预处理结束之后,我们就要尝试将 a b c d e 确定为具体的数 if(!getOperands()) return false; // 在获取完运算数之后,便可以输出我们得到的等式了 if(hasBracket[0] != -1) printf("("); printf("%d", operands[0]); for(int i = 1; i < operandNum; ++i){ if(operators[i - 1] == 13) printf("÷"); else printf("%c", op[operators[i - 1]]); if(hasBracket[i] == 14) printf("("); printf("%d", operands[i]); if(hasBracket[i] == 15) printf(")"); } printf("=%d\n", ans); return true; } // solve2() 生成真分数运算式 // 由于只要求加减法,因此括号出现与否并不会影响答案 // 故solve2()中无须考虑括号与乘除号 bool solve2(){ init3(); operatorNum = getNum(3, 5); operandNum = operatorNum + 1; for(int i = 0; i < operatorNum; ++i) operators[i] = getNum(10, 11); // 生成分子与分母,保证分子严格小于分母 denominators[0] = getNum(1, 66); numerators[0] = getNum(1, max(denominators[0] - 22, 1)); int g = __gcd(numerators[0], denominators[0]); numerators[0] /= g, denominators[0] /= g; int nowNume = numerators[0], nowDeno = denominators[0]; for(int i = 1; i < operandNum; ++i){ denominators[i] = getNum(1, 66); numerators[i] = getNum(1, max(denominators[0] - 22, 1)); g = __gcd(numerators[i], denominators[i]); numerators[i] /= g, denominators[i] /= g; g = __gcd(denominators[i], nowDeno); int lcm = nowDeno*denominators[i]/g; nowNume *= denominators[i]/g; if(operators[i - 1] == 10){ nowNume += numerators[i]*nowDeno/g; if(nowNume >= nowDeno) return false; }else if(operators[i - 1] == 11){ nowNume -= numerators[i]*nowDeno/g; if(nowNume <= 0) return false; } nowDeno = lcm; g = __gcd(nowNume, nowDeno); nowNume /= g, nowDeno /= g; // 当运算过程中分子分母大于阈值(这里是 666 ),则重新生成算式 if(nowNume > 666 || nowDeno > 666) return false; } printf("%d/%d", numerators[0], denominators[0]); for(int i = 1; i < operandNum; ++i){ printf("%c", op[operators[i - 1]]); printf("%d/%d", numerators[i], denominators[i]); } printf("=%d/%d\n", nowNume, nowDeno); return true; } // 这里 x 与模数控制分数运算式出现的概率 // 这里限定为 30% 的分数运算式, 50% 的有括号普通运算式 ,20% 的无括号普通运算式 void solve(){ int x = getNum(0, 9); if(x <= 2) while(!solve2()) ; else if(x <= 7) while(!solve1(true)) ; else while(!solve1(false)) ; } int main(){ // 初始化函数零:初始化运算符 id 符号 以及运算优先级,并且重定向输出 init0(); //读入需要生成的运算式数量 int n; scanf("%d", &n); //输出学号及运算式 puts("2017012449"); while(n--) solve(); return 0; } ``` # 5 演示图片 ![20](https://images.gitee.com/uploads/images/2019/0315/174428_0a6f502a_2349038.png) ![30](https://images.gitee.com/uploads/images/2019/0315/174428_b8a3eece_2349038.png) # 6 个人软件过程 PSP ## 6.1 代码1.0 PSP ### 6.1.1 已实现功能 - 1 生成n道数字在 0~100 之间的算术题 - 2 运算过程中不出现分数与负数 - 3 运行 .exe 生成 result.txt - 4 支持有括号的运算 ### 6.1.2 未完成功能 - 1 支持真分数的出题与运算 ### 6.1.3 PSP表格 ![PSP2.1](https://images.gitee.com/uploads/images/2019/0315/174428_a141f839_2349038.png) ## 6.2 代码1.1 PSP ### 6.2.1 更新内容 - 重新复审了代码 - 优化了代码格式 - 分段加入了注释 - 修复了一些小BUG ### 6.2.2 PSP表格 ![PSP](https://images.gitee.com/uploads/images/2019/0319/123559_50b00410_2349038.png) ## 6.3 代码1.2 PSP ### 6.3.1 更新内容 解决了被除数可能会大于100的问题 ![问题](https://images.gitee.com/uploads/images/2019/0319/123559_5c695118_2349038.png) 优化了代码框架 ### 6.3.2 PSP表格 ![PSP](https://images.gitee.com/uploads/images/2019/0319/123559_e15f619e_2349038.png) ## 6.4 代码1.3 PSP ### 6.4.1 更新内容 成功生成真分数运算式 ![源代码 4.0](https://images.gitee.com/uploads/images/2019/0319/123559_9c45f312_2349038.png) ### 6.4.2 PSP表格 ![PSP](https://images.gitee.com/uploads/images/2019/0319/123559_f30cfad5_2349038.png) ## 6.5 代码1.4 PSP ### 6.5.1 更新内容 - 封装了随机数生成函数 - 随机生成无括号运算式 ![5.0](https://images.gitee.com/uploads/images/2019/0319/123559_c1a7394f_2349038.png) ### 6.5.2 PSP表格 ![PSP](https://images.gitee.com/uploads/images/2019/0319/123559_2fa2fe2d_2349038.png) # 7 图形化界面 ## 7.1 使用工具 Qt QtCreater Qt 是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。 它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。 Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,Qt很容易扩展,并且允许真正地组件编程。 2008年,Qt Company科技被诺基亚公司收购,Qt也因此成为诺基亚旗下的编程语言工具。2012年,Qt被Digia收购。 2014年4月,跨平台集成开发环境Qt Creator 3.1.0正式发布,实现了对于iOS的完全支持,新增WinRT、Beautifier等插件,废弃了无Python接口的GDB调试支持,集成了基于Clang的C/C++代码模块,并对Android支持做出了调整,至此实现了全面支持iOS、Android、WP,它提供给应用程序开发者建立艺术级的图形用户界面所需的所有功能。 基本上,Qt 同 X Window 上的 Motif,Openwin,GTK 等图形界 面库和 Windows 平台上的 MFC,OWL,VCL,ATL 是同类型的东西。 ![Qt](https://gitee.com/uploads/images/2019/0410/083703_39ec92e6_2349038.png "897{(UVIQ61$@5B{4)J)W4Q.png") ## 7.2 功能设计 支持生成指定数量的题目,并可选择是否混杂真分数运算式子以及括号运算式 支持导出文档 支持在线做题,并记录得分 支持查看做题结果,并得知每题的正确答案 ## 7.3 版本迭代 ### 7.3.1 版本2.0 #### 7.3.1.1 更新内容 支持生成指定数量的题目,并可选择是否混杂真分数运算式子以及括号运算式 支持导出文档 #### 7.3.1.2 效果展示 初始界面 ![界面](https://gitee.com/uploads/images/2019/0410/082927_5106f280_2349038.png "`N2M17JOP29Y[@}OO)V(R[8.png") 生成题目 ![生成题目1](https://gitee.com/uploads/images/2019/0410/083209_40999c50_2349038.png "8S5N$S[9~EA)O@B{9I5K[ZY.png") ![生成题目2](https://gitee.com/uploads/images/2019/0410/083235_55ea7be5_2349038.png "}IH4N}YWMUSO31HUVO5VUWO.png") 导出题目 ![导出题目](https://gitee.com/uploads/images/2019/0410/083251_a36d6e76_2349038.png "LJWW5LIXM(IL[Y3N2]M9C}K.png") ### 7.3.2 版本2.1 #### 7.3.2.1 更新内容 (实现中)支持在线做题,并记录得分 #### 7.3.2.2 效果展示 ## 7.4 项目收获 1、这几天恶补了一下Qt编程,在Qt的GUI编程界面畅游了一番,发现Qt的signals/slot机制在界面上体现的是淋漓尽致啊。 2、深刻的体会到了图形化界面编程与控制台程序编程的不同之处:从框架设计,到实施细节、到编程实现难点,都有着天壤之别 3、个人感觉,编程最重要的是要对某个代码能够看出它的逻辑层次是否有问题,框架设计上是否有缺陷。至于框架,个人感觉无非是代码量的问题、工作量的问题,思路清晰了,语言的驾驭能力强了,这些都不是问题。 4、通过这次项目经历,体会到了算法竞赛的编程习惯与项目实践的编程风格的极大的区别,比如说在算法竞赛当中,using namespace std; 基本上是为了提高编程速度,编程效率,必不可少的声明,但是在项目当中,这其实是一种及其糟糕的习惯,大型项目的命名空间,在使用using namespace之后会产生大量的冲突; 5、还有比如在算法竞赛中,由于代码基本上在三百行以内就可以解决一个问题,所以变量起名都比较随意,比较图省事,但是在编写大型项目的时候,这会导致项目可读性变得极差,并且会在不起眼的地方产生一些不可思议的bug,初心是为了省时间,但是却花了很多不必要的在debug上