小组成员:
黄展恒 3117004611
唐成杰 3117004729
1、项目地址
https://github.com/AceWong1024/EqualtionGenerator
2、
|
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
|
Planning |
计划 |
30 |
30 |
|
· Estimate |
· 估计这个任务需要多少时间 |
30 |
30 |
|
Development |
开发 |
1160 |
1340 |
|
· Analysis |
· 需求分析 (包括学习新技术) |
120 |
120 |
|
· Design Spec |
· 生成设计文档 |
60 |
70 |
|
· Design Review |
· 设计复审 (和同事审核设计文档) |
50 |
60 |
|
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
30 |
40 |
|
· Design |
· 具体设计 |
120 |
130 |
|
· Coding |
· 具体编码 |
600 |
700 |
|
· Code Review |
· 代码复审 |
60 |
70 |
|
· Test |
· 测试(自我测试,修改代码,提交修改) |
120 |
150 |
|
Reporting |
报告 |
140 |
150 |
|
· Test Report |
· 测试报告 |
60 |
70 |
|
· Size Measurement |
· 计算工作量 |
30 |
30 |
|
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
50 |
60 |
|
合计 |
|
1330 |
1520 |
3、效能分析
利用Visual Studio自带的性能探查器,我们可以查看程序各函数的调用情况。
1、生成10道题目的情况:

2、生成10000道题目的情况

根据性能报告可以看出,本次程序的性能并不佳,少量题目速度尚可,大量题目就要生成比较久的时间了。而根据函数调用情况,CheckClass的Calculate()函数占用了大多数的时间,我们估计是计算四则运算式的算法并未过关,因此这是日后改进的方向。
4、设计实现过程
在阅读完题目后经过讨论,程序大致需要以下几个模块:
1、 随机数生成器
2、 四则运算式生成器
3、 四则运算式计算器
4、 文件读写器
分别对应题目中生成四则运算式、保存答案和校对答案几个功能。
程序思路如下:
在主程序中对程序启动所带的命令行参数进行处理,根据命令行参数选择启用的功能。
生成四则运算式则先调用随机数生成器产生所需要的随机个操作数以及运算符,然后通过四则运算生成器将操作数和运算符转换成字符串算式,然后通过文件读写器写入文件。
保存答案则利用四则运算式计算器先计算出四则运算生成器所生成的字符串算式的结果,然后通过文件读写器写入文件。
校对答案则先通过文件读写器分别读取题目和回答,然后通过四则运算式计算器计算出结果并将结果与回答对比,之后再在命令行中输出对错情况。
项目结构如下:

类:
1、 CheckClass --四则运算式计算器
2、 EqualtionGenerator –四则运算式生成器
3、 Random –随机数生成器
4、 RWOperator --文件读写器
文件:
Main.cpp --主程序
5、代码说明
Main.cpp
int main(int argc, char** argv) {
int nums = 1;
int maxnum = 0;
string exerFile, ansFile;
if (argc == 1) {
cout << "Parameter error!" << endl;
}
else {
int i = 1;
string temp;
for (; i < argc; ++i) {
temp = argv[i];
if (temp == "-n") {
nums = atoi(argv[i + 1]);
i++;
chosenProgram(nums, maxnum, exerFile, ansFile);
}
else if (temp == "-r") {
maxnum = atoi(argv[i + 1]);
i++;
chosenProgram(nums, maxnum, exerFile, ansFile);
}
else if (temp == "-a") {
ansFile = argv[i + 1];
i++;
chosenProgram(nums, maxnum, exerFile, ansFile);
}
else if (temp == "-e") {
exerFile = argv[i + 1];
i++;
chosenProgram(nums, maxnum, exerFile, ansFile);
}
else {
cout << "Parameter error! " <<endl;
errno = 1;
break;
}
/*if (errno != 1) {
cout << "hello world " << temp << endl;
}*/
}
}
system("pause");
return 0;
}
CheckClass.Calculate() --计算函数
double Calculate()//算符优先计算
{
int k = 0;//指向顶部
int j;//指向顶部终结符
int i = 0;//待进栈元素所在vector下标
string sym;//待进栈元素
string Q;//stack[j]上面的终结符
string str;//规约后的值
bool isVt = true;//若执行规约操作则设为f,如:元素3,在刚入栈时为vt,但在规约成新的3时,则不是vt了
bool isRight = true;//运算是否出错的标志
stack.push_back("=");
//sym = vstr[0];
//cout << endl << "计算中间过程: " << endl;
do
{
sym = vstr[i];
if (IsVt(stack[k]) && isVt)
j = k;
else
j = k - 1;
while (GetPri(stack[j], sym) == ">" && (j >= 0))//规约
{
do//确定哪些可规约
{
Q = stack[j];
if (IsVt(stack[j - 1]) && isVt
&& (stack[j] != ")"))//如果是(i)的形式,且i是已规约过的i,需要执行else
{
j = j - 1;
}
else
j = j - 2;
} while (GetPri(stack[j], Q) != "<");
if ((j + 1) == k)//需要规约的只有一个i
{
str = Q;
isVt = false;
}
else//3个需要规约i+i,i-i...
{
if (stack[j + 2] == "+")//三个中间的是+,则两边的i执行+操作
{
strstream ss;//必须在此声明!!!
double c = atof(stack[j + 1].c_str()) + atof(stack[j + 3].c_str());
ss << c;
ss >> str;
}
else if (stack[j + 2] == "-")//三个中间的是-,则两边i的执行-操作
{
strstream ss;
double c = atof(stack[j + 1].c_str()) - atof(stack[j + 3].c_str());
ss << c;
ss >> str;
}
else if (stack[j + 2] == "*")
{
strstream ss;
double c = atof(stack[j + 1].c_str()) * atof(stack[j + 3].c_str());
ss << c;
ss >> str;
}
else if (stack[j + 2] == "/")
{
strstream ss;
double c = atof(stack[j + 1].c_str()) / atof(stack[j + 3].c_str());
ss << c;
ss >> str;
}
else
{
str = stack[j + 2];
}
isVt = false;
}
for (int n = j + 1; n <= k; n++)//出
{
stack.pop_back();
}
stack.push_back(str);//规约、进
k = j + 1;
//测试输出
//for (int cc = 0; cc < stack.size(); cc++)
//{
// cout << stack[cc];
//}
//cout << endl;
}
if (((GetPri(stack[j], sym) == "<") || (GetPri(stack[j], sym) == "=")))//移进
{
stack.push_back(sym);
k = k + 1;
////测试输出
//for (int cc = 0; cc < stack.size(); cc++)
//{
// cout << stack[cc];
//}
//cout << endl;
}
else
{
isRight = false;
//cout << "错误" << endl;
}
i++;
isVt = true;
} while ((sym != "=") && (i < vstr.size()));
if (isRight)
return atof(stack[1].data());
else
return -1;
}
EqulationsGenerator.h --四则运算式生成器
class EqualtionGenerator
{
private:
public:
Random rander;
EqualtionGenerator();
~EqualtionGenerator();
std::vector<int> getNums(int amount,int min,int max);
std::vector<char> getOper(int amount);
string getEqual(vector<int>&, vector<char>&);
};
EqulationsGenerator.cpp
std::vector<int> EqualtionGenerator::getNums(int amount,int min,int max) {
std::vector<int> nums;
for (int i = 0; i < amount; ++i) {
nums.push_back(rander.getRanNum(min, max));
}
return nums;
}
std::vector<char> EqualtionGenerator::getOper(int amount) {
std::vector<char> ope;
char oper[4] = { '+','-','*','/' };
for (int i = 0; i < amount; ++i) {
ope.push_back(oper[rander.getRanNum(0, 3)]);
}
return ope;
}
string EqualtionGenerator::getEqual(vector<int> & num, vector<char> & oper) {
//position of brackets
int lbrack = -1, rbrack = -1;
bool flag = rander.getRanNum(0, 1) ? true : false;
if (flag) {
for (int i = 0; i < num.size() - 1; ++i) {
if (lbrack != -1) {
int isR = rander.getRanNum(lbrack + 1, num.size() - 1);
rbrack = isR;
break;
}
else {
int isL = rander.getRanNum(1, num.size()) % num.size();
if (isL == 0) {
lbrack = i;
}
}
}
}
//generate equaltions
int length = oper.size() + num.size();
string equaltion;
for (int i = 0; i < length; ++i) {
if (i % 2 == 0) {
if (flag && lbrack == i / 2) {
equaltion += '(';
equaltion += to_string(num[i / 2]);
}
else if (flag && rbrack == i / 2) {
equaltion += to_string(num[i / 2]);
equaltion += ')';
}
else {
equaltion += to_string(num[i / 2]);
}
}
else
equaltion += oper[i / 2];
}
//insert brackets
//if (flag && lbrack != -1 && rbrack != -1) {
// equaltion.insert(2 * lbrack, 1, '(');
// if (rbrack == num.size() - 1)
// equaltion.insert(2 * (rbrack+1), 1, ')');
// else
// equaltion.insert(2 * (rbrack + 1), 1, ')');
//}
return equaltion;
}
6、测试运行
生成运算式和答案:



校对答案:




7、项目小结
黄展恒:
因为平时敲代码比较少,很多基础知识都忘记了,导致很多数据类型不匹配的错误,很头疼,还得翻书看看复习复习
作业中使用了博客的方法来提交作业,对于一直想要搭建博客写博文却一直没有实现的自己无疑是极好的,既能培养自己写博客的习惯,又能提升自己的实践能力
唐成杰:
结对编程,对于我来说是一种全新的尝试,充满了未知与好奇。在作业刚发布时,我便找到我的好朋友黄展恒组成小组。对于缺乏开发经验的我来说,找到一位合适的结伴对象是非常重要的,可以和一位技艺娴熟的同学一起学习讨论,实在是再好不过。
在结对编程的过程中,看着大哥手起刀落,对于算法的不同思路想法,我们之间的讨论交流都让我受益匪浅发现了我的很多不足,同学的耐心讲解对懵懂的我宛如指路明灯。整个过程下来,相对于个人项目,分工合作和代码复查可以节省很多的时间,使效率大大提高,遇到问题时不用再闷头苦苦思索,将问题跑出来一起解决,开发的过程也比单人奋斗轻松愉悦许多。