Arithmetic Device

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 项目源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#include <bits/stdc++.h>

using namespace std;

mt19937 mt(time(0));
map<int, char> op;
map<int, int> 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<int> opr;
stack<node> 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 源代码 2.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#include <bits/stdc++.h>

using namespace std;

mt19937 mt(time(0)); // 随机数生成器

map<int, char> op; // 运算符id及其符号映射
map<int, int> 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<int> opr;
stack<node> 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 源代码 3.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201

#include <bits/stdc++.h>

using namespace std;

mt19937 mt(time(0)); // 随机数生成器

map<int, char> op; // 运算符id及其符号映射
map<int, int> 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<int> opr;
stack<node> 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 源代码 4.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262

#include <bits/stdc++.h>

using namespace std;

mt19937 mt(time(0)); // 随机数生成器

map<int, char> op; // 运算符id及其符号映射
map<int, int> 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<int> opr;
stack<node> 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 代码 5.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#include <bits/stdc++.h>

using namespace std;


mt19937 mt(time(0)); // 随机数生成器

map<int, char> op; // 运算符id及其符号映射
map<int, int> 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<int> opr;
stack<node> 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

30

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

6.2 代码2.0 PSP

6.2.1

6.2.1 更新内容

  • 重新复审了代码

  • 优化了代码格式

  • 分段加入了注释

  • 修复了一些小BUG

6.2.2 PSP表格

PSP

6.3 代码3.0 PSP

6.3.1 更新内容

解决了被除数可能会大于100的问题

问题

优化了代码框架

6.3.2 PSP表格

PSP

6.4 代码4.0 PSP

6.4.1 更新内容

成功生成真分数运算式

源代码 4.0

6.4.2 PSP表格

PSP

6.5 代码5.0 PSP

6.5.1 更新内容

  • 封装了随机数生成函数

  • 随机生成无括号运算式

5.0

6.5.2 PSP表格

PSP