github地址:https://github.com/includebug/ExerciseGenerate
结对伙伴:王煜墉(3118004972) + 徐伟浩(3118004980)
实现一个自动生成小学四则运算题目的命令行程序(也可以用图像界面,具有相似功能)。
自然数:0, 1, 2, …。
其中e, e1和e2为表达式,n为自然数或真分数。
Myapp.exe -n 10
将生成10个题目。
使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如
Myapp.exe -r 10
将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。
四则运算题目1
四则运算题目2
……
其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。
在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:
答案1
答案2
特别的,真分数的运算如下例所示:1/6 + 1/8 = 7/24。
Myapp.exe -e 
统计结果输出到文件Grade.txt,格式如下:
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
其中“:”后面的数字5表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) | 
|---|---|---|---|
| Planning | 计划 | 20 | 20 | 
| · Estimate | · 估计这个任务需要多少时间 | 1260 | 1400 | 
| Development | 开发 | 900 | 1030 | 
| · Analysis | · 需求分析 (包括学习新技术) | 30 | 60 | 
| · Design Spec | · 生成设计文档 | 30 | 20 | 
| · Design Review | · 设计复审 (和同事审核设计文档) | 20 | 20 | 
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 20 | 
| · Design | · 具体设计 | 30 | 30 | 
| · Coding | · 具体编码 | 50 | 50 | 
| · Code Review | · 代码复审 | 30 | 25 | 
| · Test | · 测试(自我测试,修改代码,提交修改) | 30 | 25 | 
| Reporting | 报告 | 60 | 60 | 
| · Test Report | · 测试报告 | 20 | 20 | 
| · Size Measurement | · 计算工作量 | 20 | 20 | 
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 20 | 
| 合计 | 1280 | 1420 | 
根据用户输入的不同参数,调用不同功能的模块。

代码如下:
#include <iostream>
#include <fstream>
#include "Fraction.h"
#include "ExerciseGenerate.h"
#include "calculator.h"
using namespace std;
int main(int argc,char* argv[])
{
	wcout.imbue(locale("chs"));
	if (argc < 3)   //参数没有 4个 
	{
		cout << "输入错误参数少于3个,请到cmd下运行" << endl;
		system("pause");
		return 1;
	}
	if (strcmp(argv[1], "-n") == 0 && strcmp(argv[3], "-r") == 0)
	{
		wofstream ofs1, ofs2;
		ofs1.open("Exercise.txt", ios::app);
		ofs2.open("Result.txt", ios::app);
		ofs1.imbue(locale("chs"));
		ofs2.imbue(locale("chs"));
		int ExerciseNumber, MaxNum;
		ExerciseNumber = atoi(argv[2]);
		MaxNum = atoi(argv[4]);
		ExerciseGenerate *x = new ExerciseGenerate[ExerciseNumber];
		int j = 0;
		for (int i = 0; i < 20 && j < ExerciseNumber; i++)
		{
			x[j].ExerciseGenerateMaxNum(MaxNum);
			x[j].generateExpress();
			Expression temp = x[j].returnExpression();
			Fraction z = temp.returnResult();
			if (z.isPositive())
			{
				wstring y = temp.returnExpressionWstring();
				ofs1 << j + 1 << L"、 ";
				ofs1 << y << endl;
				y = z.FractionWstring();
				ofs2 << j + 1 << L"、 ";
				ofs2 << y << endl;
				j++;  //读取下一个表达式
				i = 0; //重新计数,超过20次不能存进去则跳出
			}
		}
		
		delete[]x;
		ofs1.close();
		ofs2.close();
	}
	else if (!strcmp(argv[1], "-e") && !strcmp(argv[3], "-a"))
	{
		string ExerciseFile = argv[2];
		string AnswerFile = argv[4];
		ReadANSI(ExerciseFile, AnswerFile);
	}
	system("pause");
	return 0;
}
计算器是整个程序中难度最大的一部分,是后面判断表达式的值与用户所填写的值是否相同的关键。实现计算器的算法有很多,如栈,递归,或者是利用后序遍历二叉树的方法。这里我们采用最为暴力的一种解法,这就利用if else进行判断求值。流程图如下:

代码如下:
#include "calculator.h"
#include "Fraction.h"
int simplify(int a, int b)  //求最大公约数
{
	int c = 0;
	while (c = a % b)   //c==0时 循环结束
	{
		a = b;
		b = c;
	}
	return b;
}
bool isNo(wchar_t c) //is Number
{
	return c >= L‘0‘ && c <= L‘9‘ || c == L‘\‘‘ || c == L‘/‘;
}
bool isSyb(wstring c)//is Symbol
{
	return (c[0] == L‘+‘ || c[0] == L‘-‘ || c[0] == L‘ב || c[0] == L‘÷‘) && c.size() == 1; //ver1.2
}
bool isSyb(wchar_t c)//is Symbol
{
	return c == L‘+‘ || c == L‘-‘ || c == L‘ב || c == L‘÷‘;
}
bool isIlg(wchar_t c) //is illegal
{
	return !isNo(c) && !isSyb(c) && !(c == L‘(‘ || c == L‘)‘ || c == L‘ ‘);
}
wstring GetSubtext(wstring S, size_t bgn, size_t end) //follow "left-inclusive interval"
{
	wstring subtext(end - bgn, L‘ ‘);
	for (size_t i = bgn; i != end; i++)
		subtext[i - bgn] = S[i];
	return subtext;
}
vector<wstring> split(const std::wstring &s, wchar_t delim)  //切割wstring,以delim符号为分界
{
	if (!s.find(delim))
	{
		cout << "没有这个字符,该数不是分数" << endl;
	}
	vector<wstring> elems;
	wstringstream ss;
	ss.str(s);
	std::wstring item;
	while (getline(ss, item, delim)) {
		elems.push_back(item);
	}
	return elems;
}
bool spiltNum(int a[3], wstring x)  //把分数的wstring传进去,返回三个整数,存在数组a里
{
	vector<wstring> k, k1;
	k = split(x, L‘\‘‘);
	if (k.at(0) == x)    //判断是否切割成功
		return false;
	k1 = split(k.at(1), L‘/‘);
	if (k1.at(0) == k.at(1))
		return false;
	a[0] = stoi(k.at(0));
	a[1] = stoi(k1.at(0));
	a[2] = stoi(k1.at(1));
	return true;
}
wstring calculatorFractionAndInt(wstring x, wstring y, char z)
{
	int b = stoi(y);
	int a[3];
	spiltNum(a, x);
	int c[3];
	wstring TempResult;
	if (z == ‘+‘)
	{
		c[0] = a[0] + b;
		c[1] = a[1];
		c[2] = a[2];
	}
	else if (z == ‘-‘)
	{
		c[0] = a[0] - b;
		c[1] = a[1];
		c[2] = a[2];
	}
	else if (z == ‘*‘)
	{
		c[0] = a[0] * b;
		c[1] = a[1] * b;
		c[2] = a[2];
	}
	else
	{
		c[0] = 0;
		c[1] = a[0] * a[2] + a[1];
		c[2] = a[2] * b;
	}
	if (c[2] != 0 && c[1] != 0)
	{
		int d = simplify(c[1], c[2]);
		c[1] /= d;
		c[2] /= d;
	}
	if (c[1] > c[2] && c[2] != 0)
	{
		c[0] += c[1] / c[2];
		c[1] = c[1] % c[2];
	}
		
	TempResult = to_wstring(c[0]) + L‘\‘‘ + to_wstring(c[1]) + L‘/‘ + to_wstring(c[2]);
	return TempResult;
}
wstring calculatorIntAndFraction(wstring x, wstring y, char z)
{
	int a = stoi(x);
	int b[3];
	spiltNum(b, y);
	int c[3];
	wstring TempResult;
	if (z == ‘+‘)
	{
		c[0] = a + b[0];
		c[1] = b[1];
		c[2] = b[2];
	}
	else if (z == ‘-‘)
	{
		c[0] = a - b[0] - 1;
		c[1] = b[2] - b[1];
		c[2] = b[2];
	}
	else if (z == ‘*‘)
	{
		c[0] = b[0] * a;
		c[1] = b[1] * a;
		c[2] = b[2];
	}
	else
	{
		c[0] = 0;
		c[1] = b[0] * b[2] + b[1];
		c[2] = b[2] * a;
	}
	if (c[2] != 0 && c[1] != 0)
	{
		int d = simplify(c[1], c[2]);
		c[1] /= d;
		c[2] /= d;
	}
	if (c[1] > c[2] && c[2] != 0)
	{
		c[0] += c[1] / c[2];
		c[1] = c[1] % c[2];
	}
	TempResult = to_wstring(c[0]) + L‘\‘‘ + to_wstring(c[1]) + L‘/‘ + to_wstring(c[2]);
	return TempResult;
}
wstring calculatorFraction(wstring x, wstring y, char z)
{
	int a[3], b[3];
	spiltNum(a, x);
	spiltNum(b, y);
	int c[3];
	wstring TempResult;
	if (z == ‘+‘)
	{
		c[0] = a[0] + b[0];
		c[1] = a[1] * b[2] + a[2] * b[1];
		c[2] = a[2] * b[2];
	}
	else if (z == ‘-‘)
	{
		c[0] = a[0] - b[0];
		c[1] = a[1] * b[2] - a[2] * b[1];
		c[2] = a[2] * b[2];
		if (c[1] < 0)
		{
			c[0]--;
			c[1] = c[2] - c[1];
		}
	}
	else if (z == ‘*‘)
	{
		c[0] = 0;
		c[1] = (a[0] * a[2] + a[1])*(b[0] * b[2] + b[1]);
		c[2] = a[2] * b[2];
	}
	else
	{
		c[0] = 0;
		c[1] = (a[0] * a[2] + a[1])*b[2];
		c[2] = a[2] * (b[0] * b[2] + b[1]);
	}
	if (c[2] != 0 && c[1] != 0)
	{
		int d = simplify(c[1], c[2]);
		c[1] /= d;
		c[2] /= d;
	}
	if (c[1] > c[2] && c[2] != 0)
	{
		c[0] += c[1] / c[2];
		c[1] = c[1] % c[2];
	}
	TempResult = to_wstring(c[0]) + L‘\‘‘ + to_wstring(c[1]) + L‘/‘ + to_wstring(c[2]);
	return TempResult;
}
wstring calculatorInt(wstring x, wstring y, char z)
{
	int a[3];
	wstring CtmpResult;
	int tempResult;
	if (z == ‘+‘)
	{
		tempResult = stoi(x) + stoi(y);
	}
	if (z == ‘-‘)
	{
		tempResult = stoi(x) - stoi(y);
	}
	if (z == ‘*‘)
	{
		tempResult = stoi(x) * stoi(y);
	}
	if (z == ‘/‘)
	{
		if (stoi(y) == 0)
		{
			CtmpResult = L"0\‘0/0";
			return CtmpResult;
		}
		else if (stoi(x) % stoi(y) == 0)
			tempResult = stoi(x) / stoi(y);
		else
		{
			int num1 = stoi(x), num2 = stoi(y);
			a[0] = num1 / num2;
			a[1] = num1 % num2;
			a[2] = num2;
			CtmpResult = to_wstring(a[0]) + L‘\‘‘ + to_wstring(a[1]) + L‘/‘ + to_wstring(a[2]);
			return CtmpResult;
		}
	}
	CtmpResult = to_wstring(tempResult); // int to wstring
	return CtmpResult;
}
wstring BasicCalculation(wstring x, wstring y, char z)
{
	wstring CtmpResult;
	if (string::npos != x.find(L"\‘") && string::npos != y.find(L"\‘"))  //两个分数计算
		CtmpResult = calculatorFraction(x, y, z);
	else if (string::npos == x.find(L"\‘") && string::npos != y.find(L"\‘"))  //左边整数,右边分数
		CtmpResult = calculatorIntAndFraction(x, y, z);
	else if (string::npos != x.find(L"\‘") && string::npos == y.find(L"\‘"))  //左边分数,右边整数
		CtmpResult = calculatorFractionAndInt(x, y, z);
	else                                                                           //两个整数
		CtmpResult = calculatorInt(x, y, z); // int to wstring
	return CtmpResult;
}
wstring calc(wstring S, size_t len)
{
	deque<wstring> words;
	bool inNo = false; //door for isNo
	//bool inSyb = false; //door for isSyb
	size_t NoPos = 0;
	//size_t SybPos = 0;
	size_t ParPos = 0, ParDep = 0;
	size_t begin = 0, end = len;
	for (size_t temp = 0; temp < len; temp++)
	{
		if (S[temp] == L‘、‘)
			begin = temp + 1;
		if (S[temp] == L‘=‘)
			end = temp;
	}
	for (size_t i = begin; i <= end; i++) //build standard formula
	{
		if (isIlg(S[i]) && i != end) //formula illegal checking
			return L"The formula is illegal1!";
		if (isNo(S[i]) && !inNo) //Number postion begin
		{
			inNo = true;
			NoPos = i;
		}
		if (!isNo(S[i]) && inNo) //Number postion end
		{
			inNo = false;
			words.push_back(GetSubtext(S, NoPos, i));
		}
		if (isSyb(S[i])/* && !inSyb*/)
		{
			words.push_back(GetSubtext(S, i, i + 1));
		}
		if (S[i] == L‘)‘)
			return L"RightParentheses is not match!";
		if (S[i] == L‘(‘)
		{
			//inPar = true;
			ParDep++;
			ParPos = i + 1; //left-inclusive interval
			size_t j;
			for (j = i + 1; ParDep && j <= end; j++)
			{
				if (S[j] == L‘(‘) ParDep++;
				if (S[j] == L‘)‘) ParDep--;
			}
			if (j <= end)
			{
				wstring recResult(calc(GetSubtext(S, ParPos, j - 1), GetSubtext(S, ParPos, j - 1).size()));
				if (recResult == L"The formula is illegal!")
					return L"The formula is illegal2!";
				else if (recResult == L"Parentheses is not match!")
					return L"LeftParentheses is not match!";
				else
					words.push_back(recResult);
			}
			else
			{
				//parentheses is not match
				return L"LeftParentheses is not match!";
			}
			i = j - 1; //jump over subtext
		}
	} //end for
	int tempResult = 0;
	wstring CtmpResult;
	//check formula
	if (words.size() == 0) return L"0"; // 0 element
	if (words.size() == 1) //1 element
		if (!isSyb(words.front()))
		{
			//strcpy_s(CtmpResult, words.front().c_str());
			CtmpResult = words.front();
			return CtmpResult;
		}
		else
		{
			wstring s(L"The formula is illegal3!");
			return s;
		}
	if (words.front() == L"×" || words.front() == L"÷")  //front is symble
		return L"The formula is illegal4!";
	if (words.front() == L"+" || words.front() == L"-")
	{
		if (!isSyb(*(words.begin() + 1)))
		{
			words.front() += *(words.begin() + 1);
			words.erase(words.begin() + 1); //erase second one
		}
		else
			return L"The formula is illegal5!";
	}
	if (isSyb(words.back()))
	{
		wcout.imbue(locale("chs"));
		wcout << words.back() << endl;
		return L"The formula is illegal6!";
	}
	for (deque<wstring>::iterator i = words.begin() + 1; i < words.end(); i++)
	{
		if ((*i == L"×" || *i == L"÷") && (isSyb(*(i - 1))) && (*(i - 1)).size())
			return L"The formula is illegal7!";
		if ((*i == L"+" || *i == L"-") && (isSyb(*(i - 1))) && (*(i - 1)).size())
			if (!isSyb(*(i + 1)))
			{
				*(i + 1) = *i + *(i + 1);
				i = words.erase(i);
			}
			else
				return L"The formula is illegal8!";
	}
	//start calculate
	for (deque<wstring>::iterator i = words.begin(); i != words.end(); i++)
	{
		//calculate * and /
		if (*i == L"×")
		{
			CtmpResult = BasicCalculation(*(i - 1), *(i + 1), ‘*‘);
			i = words.erase(i - 1);
			i = words.erase(i);
			*i = CtmpResult;
		}
		if (*i == L"÷")
		{
			CtmpResult = BasicCalculation(*(i - 1), *(i + 1), ‘/‘);
			i = words.erase(i - 1);   //erase函数返回下一个的对象,相当于数组的i++
			i = words.erase(i);
			*i = CtmpResult;
		}
	}
	for (deque<wstring>::iterator i = words.begin(); i != words.end(); i++)
	{
		//calculate + and -
		if (*i == L"+")
		{
			CtmpResult = BasicCalculation(*(i - 1), *(i + 1), ‘+‘);
			i = words.erase(i - 1);
			i = words.erase(i);
			*i = CtmpResult;
		}
		if (*i == L"-")
		{
			CtmpResult = BasicCalculation(*(i - 1), *(i + 1), ‘-‘);
			i = words.erase(i - 1);
			i = words.erase(i);
			*i = CtmpResult;
		}
	}
	CtmpResult = words.front();
	return CtmpResult;
}
void ReadANSI(string expressionFilename, string resultFilename)  //ANSI编码的txt读取
{
	wstring b;
	wstring c;
	wifstream infile;
	wifstream infile1;
	infile.imbue(locale("chs"));
	infile1.imbue(locale("chs"));
	wcout.imbue(locale("chs"));
	//ANSI型保存的txt才能用
	infile.open(expressionFilename);
	infile1.open(resultFilename);
	if (!infile.is_open())
	{
		exit(EXIT_FAILURE);
	}
	int i = 0;
	while (!infile.eof())
	{
		i++;
		getline(infile, b);
		getline(infile1, c);
		if (b.size() == 0 && c.size() == 0)
			return;
		wstring result1 = calc(b, b.size());
		if (string::npos == result1.find(L"\‘"))
			result1 = result1 + L"\‘0/1";
		Fraction temp(result1);
		wcout << temp.FractionWstring() << endl;
		if (string::npos == c.find(temp.FractionWstring()))
			cout << "error:" << i << endl;
		else
			cout << "right:" << i << endl;
		wcout << b << endl;
	}
	infile.close();
}
void ReadUTF8(string expressionFilename, string resultFilename)  //不带BOM的utf8的读取
{
	wstring_convert<codecvt_utf8<wchar_t> > conv;
	ifstream ifs(expressionFilename);
	ifstream ifs1(resultFilename);
	if (!ifs.is_open())
	{
		exit(EXIT_FAILURE);
	}
	int i = 0;
	while (!ifs.eof())
	{
		i++;
		string line1;
		getline(ifs, line1);
		wstring wb1 = conv.from_bytes(line1);
		string line2;
		getline(ifs, line2);
		wstring wb2 = conv.from_bytes(line2);
		wcout << calc(wb1, wb1.size()) << endl;
		if (string::npos == wb2.find(calc(wb1, wb1.size())))
			cout << "error:" << i << endl;
		else
			cout << "right:" << i << endl;
		wcout << wb1 << endl;
	}
	ifs.close();
}
利用c语言的rand函数生成随机数量的n个操作数以及n-1个操作符生成不同的表达式。代码如下:
#include "ExerciseGenerate.h"
bool ExerciseGenerate::ExerciseGenerateMaxNum(int i)
{
	numSize = i;
	return true;
}
wstring ExerciseGenerate::generateExpress()
{
	srand(clock());
	int i = rand() % 3 + 2;
	for (int k = 0; k < i; k++)          //3*3的二维数组存数据,一行为一个数据
	{
		int isFraction = rand() % 2;     //是否为整数,若为整数a[k][1]=0,即分子置为0
		if (isFraction == 0)
		{
			for (int j = 0; j < 3; j++)
			{
				if (j == 2)  //保证分母不为0
					a[k][j] = (rand() % (10 - 1)) + 1;
				else
					a[k][j] = rand() % 10;
			}
				
		}
		else
		{
			for (int j = 0; j < 3 ; j++)
			{
				if (j == 2)
					a[k][j] = (rand() % (10 - 1)) + 1;
				else
					a[k][j] = rand() % 10;
			}
			a[k][1] = 0;
		}
	}
	wchar_t oprtr[3];
	for (int p = 0; p < i-1; p++)
	{
		int u = rand() % 4;
		if (u == 0)
			oprtr[p] = L‘+‘;
		else if(u==1)
			oprtr[p] = L‘-‘;
		else if (u == 2)
			oprtr[p] = L‘ב;
		else 
			oprtr[p] = L‘÷‘;
	}
	int bracketsNum[2];
	if (i == 2)
	{
		bracketsNum[0] = 0;
		bracketsNum[1] = 0;
	}
	else if (i == 3)
	{
		bracketsNum[0] = rand() % 2;
		if (bracketsNum[0] == 0)
			bracketsNum[1] = 0;
		else
			bracketsNum[1] = rand() % 2;
	}
	else
	{
		bracketsNum[0] = rand() % 3;
		if (bracketsNum[0] == 0)
			bracketsNum[1] = 0;
		else if(bracketsNum[0] == 1)
			bracketsNum[1] = rand() % 5;
		else
			bracketsNum[1] = rand() % 4;
	}
	cout << bracketsNum[0] << bracketsNum[1] << endl;
	for (int x = 0; x < i; x++)
	{
		for (int y = 0; y < 3; y++)
		{
			cout << a[x][y];
		}
		cout << endl;
	}	
	Fraction fraction[4];
	for (int z = 0; z < i; z++)
	{
		wstring ex = to_wstring(a[z][0]) + L"\‘" + to_wstring(a[z][1]) + L"/" + to_wstring(a[z][2]);
		Fraction k(ex);
		fraction[z] = k;
		fraction[z].print();
	}
	Expression t(fraction, oprtr, i, bracketsNum);
	cout << "i的值" << i << endl;
	t.toWstring();
	t.CalculateFraction();
	t.print();
	NewExpression = t;
	return t.returnExpressionWstring();
}
Expression ExerciseGenerate::returnExpression()
{
	return NewExpression;
}




第一次听说这种一人写代码一人复审的编程方式,听老师介绍的时候感觉很有趣。第一次利用live share进行结对编程,一开始真的很不习惯,甚至出现各种翻车,各种掉线


事情进展得并没有那么顺利,总有不少的磕磕碰碰,但好在最后也完成了项目。伟浩的代码能力很强,实现了很多复杂的功能,错误也很少。
一个自动生成小学四则运算题目的命令行程序(C++)(王煜墉+徐伟浩)
原文:https://www.cnblogs.com/handsomeyong/p/12700938.html