四则运算题目生成及校对
这个作业属于哪个课程 | 软件工程2024 - 广东工业大学 |
---|---|
这个作业要求在哪里 | 结对项目 |
这个作业的目标 | 学习应用两人合作项目的步骤和方法 |
GitHub地址 | Four-calculation-problem-generator |
合作同学名字 | 黄立韬 张树程 |
需求分析
设计一个程序可以用来生成四则运算题目,并且生成答案且可以校对答案,要求可以通过命令行或者图像界面进行交互。
效能分析
通过生成10000道分数题目 和 10000 道整数题目 得到效能分析图
通过分析图可以看出生成相同的数目的题目,生成分数题目会比生成整数题目消耗更多的CPU资源。
原因分析:为了让生成的题目不重复,该程序使用了map容器来标记用过的题目,而分数会比整数多占用一倍的存储空间,考虑题目为3个元素的四则计算题,在生成题目数据这部分数据处理的时间复杂度会是生成整数题目的题目的时间复杂度的\({(log(n))}^3\)倍
设计实现过程
考虑到生成的题目中会出现除号、分数符合等混用的情况,考虑了将所有数据存储到.html文件,在用浏览器打开.html文件,实现展现题目,判断错误,查看答案的交互。
各个函数的调用层次结构:
代码说明
在程序中表示一个题目
思路每一个题目都用一个vector来存储,从左到右整数用一个整数,分数用两个整数连着存储,每个数之间用运算符号隔开,运算符号用负数表示,每一个负数对应一个运算符号
生成整数部分
//用于生成整数的题目
std::vector<std::vector<int> > naturalNumber(int n, int l, int r)
{
//初始化随机数生成器
srand(static_cast<unsigned int>(time(nullptr)));
if (l > r) std::swap(l, r);
//用map容器来标记每一个用过的题目,以达到不重复的效果
std::map<int, std::map<int, std::map<int, std::map<int, std::map<int, int> > > > >vis;
//将每一个式子用一个vector数组存储
std::vector<std::vector<int> > res;
while (n)
{
int x = rand() % (r - l + 1) + l;
int y = rand() % (r - l + 1) + l;
int z = rand() % (r - l + 1) + l;
int i = rand() % 4;
int j = rand() % 4;
if (vis[x][y][z][i][j]) continue;
vis[x][y][z][i][j] = true;
n--;
//随机设置是否有括号和括号的位置
if (rand() % 2)
{
res.push_back({ x,-i - 1,y,-j - 1,z,-5 });
}
else
{
if (rand() % 2)
{
res.push_back({ -6,x,-i - 1,y,-7,-j - 1,z,-5 });
}
else
{
res.push_back({ x,-i - 1,-6,y,-j - 1,z,-7,-5 });
}
}
}
return res;
}
生成分数部分
//生成分数题目
std::vector<std::vector<int> > Fraction(int n, int Numerator, int denominator)
{
//初始化随机数生成器
srand(static_cast<unsigned int>(time(nullptr)));
//将每一个式子用一个vector数组存储
std::vector<std::vector<int> > res;
//用map容器来标记每一个用过的题目,以达到不重复的效果
std::map<int, std::map<int, std::map<int, std::map<int, std::map<int, std::map<int, std::map<int, std::map<int, int> > > > > > > >vis;
while (n)
{
int x1 = rand() % Numerator + 1, x2 = rand() % denominator + 1;
int y1 = rand() % Numerator + 1, y2 = rand() % denominator + 1;
int z1 = rand() % Numerator + 1, z2 = rand() % denominator + 1;
int i = rand() % 4;
int j = rand() % 4;
if (vis[x1][x2][y1][y2][z1][z2][i][j]) continue;
n--;
vis[x1][x2][y1][y2][z1][z2][i][j] = true;
//随机设置是否有括号和括号的位置
if (rand() % 2)
{
res.push_back({ x1,x2,-i - 1,y1,y2,-j - 1,z1,z2,-5 });
}
else
{
if (rand() % 2)
{
res.push_back({ -6,x1,x2,-i - 1,y1,y2,-7,-j - 1,z1,z2,-5 });
}
else
{
res.push_back({ x1,x2,-i - 1,-6,y1,y2,-j - 1,z1,z2,-7,-5 });
}
}
}
return res;
}
计算答案
首先将用vector存储的题目进行处理,去除等于号,把vector所有表示整数的数据,转换为分数的表示形式。
然后再根据括号运算符的位置及运算符的优先级进行计算,用辗转相除法计算分子分母的最大公约数进行约分。
//用辗转相除法计算最大公约数,用来给分数化简
long long gcd(long long a, long long b) {
return b == 0 ? a : gcd(b, a % b);
}
//将分数根据不同的运算符进行计算
void calculate(long long &fz, long long &fm, long long x, long long y, long long op)
{
if (op == -1)
{
x *= fm;
fz *= y;
fm *= y;
fz += x;
}
else if (op == -2)
{
x *= fm;
fz *= y;
fm *= y;
fz -= x;
}
else if (op == -3)
{
fz *= x;
fm *= y;
}
else
{
fz *= y;
fm *= x;
}
long long u = gcd(fz, fm);
fz /= u;
fm /= u;
return;
}
//计算式子的结果,并转为字符串返回
std::string GetAns(const std::vector<int> &Data)
{
std::string res = {};
std::vector<long long> tmp;
int len = (int)Data.size();
for (int i = 0; i < len; i++)
{
tmp.push_back(Data[i]);
//将整数转为分数
if (Data[i] >= 0 && ((i == 0 && Data[i + 1] < 0) || (i != 0 && i + 1 != len && Data[i - 1] < 0 && Data[i + 1] < 0)))
{
tmp.push_back(1);
}
}
tmp.pop_back();
len = (int)tmp.size();
long long fz = 1, fm = 1;
//根据括号所在的不同位置进行计算
if (tmp[len - 1] == -6)
{
//11/(11/11)
fz = tmp[4];
fm = tmp[5];
calculate(fz, fm, tmp[7], tmp[8], tmp[6]);
int tfz = fz, tfm = fm;
fz = tmp[0];
fm = tmp[1];
calculate(fz, fm, tfz, tfm, tmp[2]);
}
else if (tmp[0] == -6)
{
//(11/11)/11
fz = tmp[1];
fm = tmp[2];
calculate(fz, fm, tmp[4], tmp[5], tmp[3]);
calculate(fz, fm, tmp[8], tmp[9], tmp[7]);
}
else
{
// 11/11/11
if (tmp[2] >= -2)
{
fz = tmp[3];
fm = tmp[4];
calculate(fz, fm, tmp[6], tmp[7], tmp[5]);
int tfz = fz, tfm = fm;
fz = tmp[0];
fm = tmp[1];
calculate(fz, fm, tfz, tfm, tmp[2]);
}
else
{
fz = tmp[0];
fm = tmp[1];
calculate(fz, fm, tmp[3], tmp[4], tmp[2]);
calculate(fz, fm, tmp[6], tmp[7], tmp[5]);
}
}
if (fz == 0 || fm == 0)
{
res += "0";
return res;
}
if (fz < 0 && fm < 0)
{
fz *= -1;
fm *= -1;
}
if (fz < 0)
{
fz *= -1;
res += '-';
}
if (fm < 0)
{
fm *= -1;
res += '-';
}
//将分数转换为带分数
if (fz / fm)
{
res += NumToString(fz / fm);
}
if (fz % fm)
{
if (fz / fm)
{
res += "\\'";
}
fz %= fm;
res += NumToString(fz);
res += "/";
res += NumToString(fm);
}
if (!res.size()) res = "0";
return res;
}
运行测试
对生成题目的形式是否符合规范进行测试
运行生成10个整数题目和10个分数题目
对答案计算进行测试
通过生成10道整数和10道分数题目,并输入手算结果进行校对
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 10 | 15 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 300 | 120 |
· Design Spec | · 生成设计文档 | 240 | 300 |
· Design Review | · 设计复审 (和同事审核设计文档) | 120 | 150 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 60 | 20 |
· Design | · 具体设计 | 180 | 180 |
· Coding | · 具体编码 | 480 | 520 |
· Code Review | · 代码复审 | 60 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 40 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 60 | 40 |
· Size Measurement | · 计算工作量 | 60 | 40 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 40 |
合计 | 1750 | 1525 |
项目小结
两人合作项目会一起产生很多对解决问题和实现功能的想法,通过充分的沟通和一些工具的利用提升了合作完成项目的效率。