软件工程——结对项目——四则运算题目生成

软件工程——结对项目——四则运算题目生成

队友博客地址:https://blog.csdn.net/qq_37248820/article/details/86301669
-------------------------------------------------------------------------------------------------------------------
实现一个能够自动生成小学四则运算题目的Windows控制台程序,最后扩展为一个Windows图形界面程序。

第一阶段:
a. 能够生成一千道小学四则运算题目到一个文件里,保证合法、不重复。
b. 可以设置乘方的符号选择( ** 或 ^ )。
c. 能解最多包含10个运算符的表达式(其中括号数不受限制,运算符有+,-,*,/,^ (或 **) 五种)。
d. 除了整数之外,还支持真分数的四则运算。
e. 能接受用户输入的答案,并判定对错,统计总共的对/错数量。

第二阶段:
和队友商量后,决定选择第一个扩展方向进行扩展。
a. 把程序变成一个Windows电脑图形界面的程序。
b. 增加一个日历显示器,显示当前时间。
c. 增加倒计时功能,每个题目必须在20秒内完成。
d. 增加历史记录功能,可以保存并展示用户的做题成绩记录。
-------------------------------------------------------------------------------------------------------------------

一、GitHub项目地址

项目的GitHub地址:https://github.com/BeckyYang/Arithmetic_SoftwareEngineeringTeamProject

二、PSP表格

PSP2.1Personal Software Process Stages预估耗时(min)实际耗时(min)
Planning计划2020
Estimate估计这个任务需要多少时间2020
Development开发28802160
Analysis需求分析(包括学习新技术)14401460
Design Spec生成设计文档00
Design Review设计复审(和同事审核设计文档)00
Coding Standard代码规范(为目前的开发制定合适的规范)00
Design具体设计6060
Coding具体编码57605760
Code Review代码复审60120
Test测试(自我测试,修改代码,提交修改)10002000
Reporting报告00
Test Report测试报告60120
Size Measurement计算工作量600
Postmortem & Process Improvement Plan事后总结,并提出过程改进计划60180
合计1136011900

三、实现思路

拿到题目之后,和队友讨论了一下分工合作,决定我负责题目生成部分,她负责题目求解部分,最后的扩展部分由我们两人共同完成。

题目分为两种类型:整数四则运算和分数四则运算,不包括整数和分数混合运算。

3.1 控制台程序的题目生成

初步思路:

  • 将分数用一个类来表示,包括分子和分母属性。分数的随机生成可通过随机生成分子和分母来实现。
  • 通过rand()函数来随机生成数和运算符(控制在1到10个之间,不包括括号),将二者组合可得到不同的表达式。
  • 再通过rand()随机生成括号。

后来在初步实现时,发现出现了随机生成的数太大,乘方的幂不是整数,表达式包含的运算符普遍偏多,随机数每次重复出现,没有做到真正随机化的情况,这样的难度并不适合小学生。于是做了如下的改进。

改进思路:

  • 将随机生成的数%10,这样可控制数字在0到9范围内。
  • 乘方后面的数采用整数形式,将随机生成的乘方的幂%3,这样可以控制幂在0到2范围内。
  • 采用正态分布来控制运算符个数和括号个数,使得大部分表达式的运算符个数为3,括号个数为3。
  • 使用 time函数来获得系统时间,然后将time_t型数据转化为(unsigned)型再传给随机数引擎。说明:一个给定的随机数发生器一直会生成相同的随机数序列,所以随机数引擎的参数一般用time(NULL),因为系统的时间一直在变,所以rand()获得的数,也就一直在变,相当于是随机数了。
  • 判重部分:主要通过运算过程来判断题目是否重复。通过对操作数栈和运算符栈进行操作,将目前已生成的所有不重复题目的运算过程保存在运算过程记录区stack_order[ ][ ]中。之后,每生成一个题目,将其运算过程与stack_order中的每一行进行比较,若相同,则重复,重新生成题目;否则,将其运算过程存入stack_order[ ][ ]中。

3.2 Windows图形界面程序的题目生成

主要设计4个窗体界面:

  • 欢迎主界面:含有一个“开始”按键,用户点击即可进入到操作请求界面。
  • 操作请求界面:含有一个日历显示器,显示当前日期。用户输入用户名、题目数量和题目类型(默认为A(没有乘方的普通运算),也可选择B(乘方用^表示)或C(乘方用**表示))并点击Begin按键,即可开始答题。点击Record按键可查询历史答题记录(每一条记录由题目,True or False , 用户答案, 正确答案组成,因超时而未做答的题不予以记录);点击Quit按键,可推出整个程序。
  • 答题界面:输入答案,点击OK键可提交答案(答案必须为最简形式,假分数需表示为整数+(或-)真分数),提交后显示答题结果和正确答案;另外在答题界面设置一个20秒的倒计时器,时间一到,提醒用户并切换到下一题。点击Cancle键可提出答题界面,回到操作请求界面,继续操作。
  • 记录查询界面:点击Query即可查询历史记录;点击Quit即可回到操作请求界面,执行其他操作。

基于C++版本的实现改进:

  • 由于没有C#的基础,所以只能边学边做。为了控制题目的长度,在用C#实现满足正态分布的随机数时,因为没有现成的函数可以调用,所以参照了网上的部分代码。在未采用之前,题目长度普遍偏长,难度偏大,不适合小学生做。(参照的正态分布随机函数代码地址
  • 另外,C#版本相对于C++版本的控制台程序作了一定改进:在判重时,将整数也用分数表示,统一采用分数类fraction的重载运算符来计算,这样可以很好地解决“/”对于整数而言为整除的问题,使得结果更加准确。

四、生成题目代码设计实现

由于代码太长,仅说明部分函数。完整代码链接

生成题目的主要函数包括:

int GetGCD(int a, int b);  //求分子a好分母b的最大公约数

int RandomSymbolGenerate(char type);  //随机生成运算符

int RandLeftBracketGenerate();  //随机生成左括号

int RandRightBracketGenerate();  //随机生成右括号

int RandIntegerGeneate();   //随机生成0到9的整数

fraction& RandFractionGeneate();  //随机生成真分数(不包括0和1),分子和分母均在0到9之间

int IntPartialResult(int A, int B, int sym); //两个整数的四则运算

fraction& FractionPartialResult(fraction &A, fraction& B, int sym);  // 两个分数的四则运算

int Check(int puzzle[], int puzzle_len, int puzzle_num, int num_type);  //判断题目是否重复

void PuzzleGenerate(char* argv, int N, char type);  //生成N个四则表达式

生成表达式部分:通过借助随机函数rand(), 来随机生成操作数和运算符以及括号。将三者组合形成一个表达式。

  • 随机生成0到9的整数
int RandIntegerGeneate() //随机生成0到9的整数
{
	int number = rand() % 10;
	if (number < 0) number = 0;
	return number;
}
  • //随机生成真分数(不包括0和1),分子和分母均在0到9之间

fraction& RandFractionGeneate() //随机生成真分数(不包括0和1),分子和分母均在0到9之间
{
	int t;
	int rand_numerator = rand() % 10;
	if (rand_numerator <= 0) rand_numerator = 1;
	int rand_denominator = rand() % 10;
	if (rand_denominator <= 0) rand_denominator = 1;
	if (rand_numerator > rand_denominator)
	{
		t = rand_numerator;
		rand_numerator = rand_denominator;
		rand_denominator = t;
	}
	else if (rand_numerator == rand_denominator)
	{
		rand_denominator += 1;
	}
	int GCD = GetGCD(rand_numerator, rand_denominator);
	fraction number(rand_numerator/GCD, rand_denominator/GCD);
	return number;
}
  • int RandLeftBracketGenerate(); //随机生成左括号
int RandLeftBracketGenerate() //随机生成左括号
{
	int left_bracket = rand() % 2;
	return left_bracket;//1表示当前生成左括号,0表示当前不生成左括号
}
  • int RandRightBracketGenerate(); //随机生成右括号
int RandRightBracketGenerate() //随机生成右括号
{
	int right_bracket = rand() % 2;
	return right_bracket; //1表示当前生成右括号,0表示当前不生成右括号
}

判重部分:主要借助栈来使实现题目的判重。

  • 运算符优先级:
运算符( 、)+ 、-* 、/^ 、**
优先级0123
  • 记录题目运算过程流程图:
    在这里插入图片描述
  • 将当前题目的解题过程order[]与stack_order[][]中的所有题目的解题过程比较,若重复,则重新生成;否则将order[]记录到stack_order[][]中。
  • 判重部分实现代码
int Check(int puzzle[], int puzzle_len, int puzzle_num, int num_type) //判断题目是否重复
{
	int i, j, L = 1, s, A, B, t, first = 0;
	fraction FA, FB, Ft;
	stack<int> stack_integer;
	stack<fraction> stack_fraction;
	stack<int> stack_operator;
	int order[600];
	memset(order, 0, sizeof(order));
	/********整数表达式*******/
	if (num_type == 0)
	{
		for (i = 0; i < puzzle_len; i++)
		{
			if (puzzle[i] < 0) continue;
			if (puzzle[i] < 100 && puzzle[i] >=0) //遇到数字,数字直接进入stack_integer栈
			{
				stack_integer.push(puzzle[i]);
			}
			else
			{
				if (puzzle[i] == 106) //遇到左括号,左括号直接入stack_operator栈
				{
					stack_operator.push(106);
				}
				else if (puzzle[i] == 107) //遇到右括号,弹出所有运算符,直至遇到左括号
				{
					if (!stack_operator.empty())
					{
						do
						{
							s = stack_operator.top(); //运算符出栈
							stack_operator.pop();
							B = stack_integer.top(); //操作数B出栈
							stack_integer.pop();
							A = stack_integer.top(); //操作数A出栈
							stack_integer.pop();
							if (s == 100 || s == 102) //+,*法,统一按照大数在前,小数在后方式运算
							{
								if (A < B)
								{
									t = A;
									A = B;
									B = t;
								}
							}
							order[L++] = A;
							order[L++] = s;
							order[L++] = B;
							if ((s == 103 && B == 0) || ((s == 104 || s == 105) && A == 0 && B == 0))
							{
								return -1;
							}
							stack_integer.push(IntPartialResult(A, B, s)); //运算结果入栈
						} while (stack_operator.empty() == false && stack_operator.top() != 106);
						if (!stack_operator.empty()) stack_operator.pop(); //弹出左括号
					}
				}
				else
				{
					if (puzzle[i] == 104 || puzzle[i] == 105) //遇到乘方,乘方优先级最高,乘方直接进运算符栈
					{
						stack_operator.push(puzzle[i]);
					}
					else
					{
						//遇到运算符,且运算符栈为空或者栈顶运算符优先级小于当前运算符优先级,运算符直接进运算符栈
						if (stack_operator.empty() == true || (stack_operator.empty() == false && priority[stack_operator.top()-100] < priority[puzzle[i]-100])) stack_operator.push(puzzle[i]);
						else
						{
							do
							{
								s = stack_operator.top(); //运算符出栈
								stack_operator.pop();
								B = stack_integer.top(); //操作数B出栈
								stack_integer.pop();
								A = stack_integer.top(); //操作数A出栈
								stack_integer.pop();
								if ((s == 100 || s == 102) && A < B) //+,*法,统一按照大数在前,小数在后方式运算
								{
									t = A;
									A = B;
									B = t;
								}
								order[L++] = A;
								order[L++] = s;
								order[L++] = B;
								if ((s == 103 && B == 0) || ((s == 104 || s == 105) && A == 0 && B == 0))
								{
									return -1;
								}
								stack_integer.push(IntPartialResult(A, B, s)); //运算结果入栈
							} while (stack_operator.empty() == false && priority[stack_operator.top() - 100] >= priority[puzzle[i]-100]);
							stack_operator.push(puzzle[i]); //运算符进运算符栈
						}
					}	
				}
			}
		}
		if (stack_operator.empty() == false)
		{
			do
			{
				s = stack_operator.top(); //运算符出栈
				stack_operator.pop();
				B = stack_integer.top(); //操作数B出栈
				stack_integer.pop();
				A = stack_integer.top(); //操作数A出栈
				stack_integer.pop();
				if ((s == 100 || s == 102) && A < B) //+,*法,统一按照大数在前,小数在后方式运算
				{
					t = A;
					A = B;
					B = t;
				}
				order[L++] = A;
				order[L++] = s;
				order[L++] = B;
				if ((s == 103 && B == 0) || ((s == 104 || s == 105) && A == 0 && B == 0))
				{
					return -1;
				}
				stack_integer.push(IntPartialResult(A, B, s)); //运算结果入栈
			}while (stack_operator.empty() == false);
		}
		order[0] = L;
	}
	/****分数表达式****/
	else
	{
		for (i = 0; i < puzzle_len; i++)
		{
			if (puzzle[i] < 0) continue;
			if (puzzle[i] < 100 && puzzle[i] >= 0) //遇到分数,分数直接进入stack_fraction栈
			{
				fraction f(puzzle[i], puzzle[i + 2]);
				stack_fraction.push(f);
				i += 2;
			}
			else
			{
				if (puzzle[i] == 106) //遇到左括号,左括号直接入stack_operator栈
				{
					stack_operator.push(106);
				}
				else if (puzzle[i] == 107) //遇到右括号,弹出所有运算符,直至遇到左括号
				{
					s = stack_operator.top(); //运算符出栈
					do 
					{
						stack_operator.pop();
						FB = stack_fraction.top(); //操作数FB出栈
						stack_fraction.pop();
						FA = stack_fraction.top(); //操作数FA出栈
						stack_fraction.pop();
						if (s == 100 || s == 102) //+,*法,统一按照大数在前,小数在后方式运算
						{
							if (FA < FB)
							{
								Ft = FA;
								FA = FB;
								FB = Ft;
							}
						}
						order[L++] = FA.GetNumerator();
						order[L++] = 1030;
						order[L++] = FA.GetDenominator();
						order[L++] = s;
						order[L++] = FB.GetNumerator();
						order[L++] = 1030;
						order[L++] = FB.GetDenominator();
						if (FB.GetNumerator() == 0 && s == 103) return -1;
						fraction ff = FractionPartialResult(FA, FB, s);
						stack_fraction.push(ff); //运算结果入栈
					} while (stack_operator.empty() == false && (s = stack_operator.top()) != 106);
					stack_operator.pop(); //弹出左括号
				}
				else
				{
					//遇到运算符,且运算符栈为空或者栈顶运算符优先级小于当前运算符优先级,运算符直接进运算符栈
					if (stack_operator.empty() == true || (stack_operator.empty() == false && priority[stack_operator.top() - 100] < priority[puzzle[i] - 100])) stack_operator.push(puzzle[i]);
					else
					{
						do
						{
							s = stack_operator.top(); //运算符出栈
							stack_operator.pop();
							FB = stack_fraction.top(); //操作数B出栈
							stack_fraction.pop();
							FA = stack_fraction.top(); //操作数A出栈
							stack_fraction.pop();
							if ((s == 100 || s == 102) && FA < FB) //+,*法,统一按照大数在前,小数在后方式运算
							{
								Ft = FA;
								FA = FB;
								FB = Ft;
							}
							order[L++] = FA.GetNumerator();
							order[L++] = 1030;
							order[L++] = FA.GetDenominator();
							order[L++] = s;
							order[L++] = FB.GetNumerator();
							order[L++] = 1030;
							order[L++] = FB.GetDenominator();
							if (FB.GetNumerator() == 0 && s == 103) return -1;
							fraction ff = FractionPartialResult(FA, FB, s);
							stack_fraction.push(ff); //运算结果入栈
						} while (stack_operator.empty() == false && priority[stack_operator.top() - 100] >= priority[puzzle[i] - 100]);
						stack_operator.push(puzzle[i]); //运算符进运算符栈
					}
				}
			}
		}
		if (stack_operator.empty() == false)
		{
			do
			{
				s = stack_operator.top(); //运算符出栈
				stack_operator.pop();
				FB = stack_fraction.top(); //操作数B出栈
				stack_fraction.pop();
				FA = stack_fraction.top(); //操作数A出栈
				stack_fraction.pop();
				if ((s == 100 || s == 102) && FA < FB) //+,*法,统一按照大数在前,小数在后方式运算
				{
					Ft = FA;
					FA = FB;
					FB = Ft;
				}
				order[L++] = FA.GetNumerator();
				order[L++] = 1030;
				order[L++] = FA.GetDenominator();
				order[L++] = s;
				order[L++] = FB.GetNumerator();
				order[L++] = 1030;
				order[L++] = FB.GetDenominator();
				if (FB.GetNumerator() == 0 && s == 103) return -1;
				fraction ff = FractionPartialResult(FA, FB, s);
				stack_fraction.push(ff); //运算结果入栈
			} while (stack_operator.empty() == false);
		}
		order[0] = L;
	}

	/**********判断与之前的题目是否重复************/
	int flag = 0, label = 0;
	for (i = 0; i < puzzle_num; i++)
	{
		if (labels[i] != num_type || order[0] != stack_order[i][0]) continue;
		else
		{
			flag = 0;
			for (j = 1; j < stack_order[i][0]; j++)
			{
				if (stack_order[i][j] != order[j])
				{
					flag = 1;
					break;
				}
			}
			if (flag == 0)
			{
				label = 1;
				break;
			}
		}	
	}
	if (label == 1) return -1; //有重复
	else
	{
		for (j = 0; j < order[0]; j++) //登记到题目记录里
		{
			stack_order[puzzle_num][j] = order[j];
		}
		return 1; //无重复
	}
}

五、程序性能分析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由性能分析报告可知,题目生成和查重函数比较耗时,而题目生成的主要调用函数也是Check()函数。因此仅需要对查重函数Check()进行改进即可。对其的改进为:设置一个题型标记数组Labels[],在判重时,优先判断题型是否一样,再判断解题过程的长度是否一样,最后再判断解题过程内容是否完全一样,这样可以减少判重时间。

六、测试数据

主要测试了题目查重和分数类运算符重载部分。

1、分数类运算符重载测试:

因为分数类运算符重载是最底层的重要部分,会受到上层函数的多次调用, 一旦这一部分出现错误,将直接导致上层函数的错误。所以对于分数类运算符重载部分的测试是有必要的。

测试用例如下:

数A运算符数B预期结果
2/3^01
2/3*2/34/9
2/3+15/3
2/3-1/31/3
6求最大公约数102
2求最大公约数31
5求最大公约数105

2、题目查重测试:

因为题目重复主要是由加法和乘法的交换律,以及括号的左结合性导致的,所以题目查重部分的测试主要针对 + 、* 、和()以及三者的结合进行测试。

测试用例如下:

表达式1表达式2预期结果
1+2+33+(2+1)重复
1+2+33+2+1不重复
1 * 2 * 33 * 2 * 1不重复
(1+2) * 33 * (2+1)重复
1+2 ** 3 ** 12 ** 3 ** 1 + 1重复
(1+(1+2)*(3-2))+11+((3-2)*(1+2))+1)不重复
(1+(1+2)*(3-2))+11+((1+2)*(3-2))+1)重复
1+2 ** 3 ** 12 ** 3 ** 1 + 1重复
1/2 + (1/3 + 1/4)1/4 + 1/3 + 1/2重复
1/2 * (1/3 + 1/4)(1/4 + 1/3) * 1/2重复

3、测试代码

namespace ArithmeticGUI.Tests
{
    [TestClass()]
    public class ClassTests
    {
//----------------fraction.cs的单元测试------------------

        [TestMethod()]//正常情况
        public void FracionGetGCDTest1()
        {
            int x = 6;
            int y = 10;
            Assert.AreEqual(fraction.FracionGetGCD(x, y), 2);
        }
        [TestMethod()]//最大公因数为1
        public void FracionGetGCDTest2()
        {
            int x = 2;
            int y = 3;
            Assert.AreEqual(fraction.FracionGetGCD(x, y), 1);
        }
        [TestMethod()]//最大公因数为其中较小的数
        public void FracionGetGCDTest3()
        {
            int x = 5;
            int y = 10;
            Assert.AreEqual(fraction.FracionGetGCD(x, y), 5);
        }
//---------------N_Puzzle.cs的单元测试-----------------

        //--------------------题目查重测试------------------
        //加法交换+括号测试
        [TestMethod()]//测试1+2+3与3+(2+1)是否重复,预期结果为重复 -1
        public void CheckTest1()
        {
            N_Puzzle.labels[0] = 0;
            N_Puzzle.labels[1] = 0;
            int[] puzzle1 = new int[] { 1, 100, 2, 100, 3 };
            int[] puzzle2 = new int[] { 3, 100, 106, 2, 100, 1, 107 };
            int puzzle_len1 = 5;
            int puzzle_len2 = 7;
            int puzzle_num1 = 0;
            int puzzle_num2 = 1;
            int num_type1 = 0;
            int num_type2 = 0;
            N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
            Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), -1);
        }
        //加法顺序交换测试
        [TestMethod()]//测试1+2+3与3+2+1是否重复,预期结果为不重复 1
        public void CheckTest2()
        {
            N_Puzzle.labels[0] = 0;
            N_Puzzle.labels[1] = 0;
            int[] puzzle1 = new int[] { 1, 100, 2, 100, 3 };
            int[] puzzle2 = new int[] { 3, 100, 2, 100, 1 };
            int puzzle_len1 = 5;
            int puzzle_len2 = 5;
            int puzzle_num1 = 0;
            int puzzle_num2 = 1;
            int num_type1 = 0;
            int num_type2 = 0;
            N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
            Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), 1);
        }
        //乘法顺序交换测试
        [TestMethod()]//测试1*2*3与3*2*1是否重复,预期结果为不重复 1
        public void CheckTest3()
        {
            N_Puzzle.labels[0] = 0;
            N_Puzzle.labels[1] = 0;
            int[] puzzle1 = new int[] { 1, 102, 2, 102, 3 };
            int[] puzzle2 = new int[] { 3, 102, 2, 102, 1 };
            int puzzle_len1 = 5;
            int puzzle_len2 = 5;
            int puzzle_num1 = 0;
            int puzzle_num2 = 1;
            int num_type1 = 0;
            int num_type2 = 0;
            N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
            Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), 1);
        }
        //除法顺序交换测试
        [TestMethod()]//测试1/2/3与3/2/1是否重复,预期结果为不重复 1
        public void CheckTest4()
        {
            N_Puzzle.labels[0] = 0;
            N_Puzzle.labels[1] = 0;
            int[] puzzle1 = new int[] { 1, 103, 2, 103, 3 };
            int[] puzzle2 = new int[] { 3, 103, 2, 103, 1 };
            int puzzle_len1 = 5;
            int puzzle_len2 = 5;
            int puzzle_num1 = 0;
            int puzzle_num2 = 1;
            int num_type1 = 0;
            int num_type2 = 0;
            N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
            Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), 1);
        }
        //乘法交换+括号测试
        [TestMethod()]//测试(1+2)*3与3*(2+1)是否重复,预期结果为重复 -1
        public void CheckTest5()
        {
            N_Puzzle.labels[0] = 0;
            N_Puzzle.labels[1] = 0;
            int[] puzzle1 = new int[] { 106, 1, 100, 2, 107, 102, 3 };
            int[] puzzle2 = new int[] { 3, 102, 106, 2, 100, 1 ,107 };
            int puzzle_len1 = 7;
            int puzzle_len2 = 7;
            int puzzle_num1 = 0;
            int puzzle_num2 = 1;
            int num_type1 = 0;
            int num_type2 = 0;
            N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
            Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), -1);
        }
        //^乘方测试
        [TestMethod()]//测试1+2^3^1与2^3^1 + 1是否重复,预期结果为重复 -1
        public void CheckTest6()
        {
            N_Puzzle.labels[0] = 0;
            N_Puzzle.labels[1] = 0;
            int[] puzzle1 = new int[] { 1, 100, 2, 104, 3, 104, 1 };
            int[] puzzle2 = new int[] { 2, 104, 3, 104, 1, 100, 1 };
            int puzzle_len1 = 7;
            int puzzle_len2 = 7;
            int puzzle_num1 = 0;
            int puzzle_num2 = 1;
            int num_type1 = 0;
            int num_type2 = 0;
            N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
            Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), -1);
        }
        //括号嵌套测试
        [TestMethod()]//测试 (1+(1+2)*(3-2))+1 与 1+((3-2)*(1+2))+1) 是否重复,预期结果为不重复 1
        public void CheckTest7()
        {
            N_Puzzle.labels[0] = 0;
            N_Puzzle.labels[1] = 0;
            int[] puzzle1 = new int[] { 106, 1, 100, 106, 1, 100, 2, 107, 102, 106, 3, 101, 2, 107, 107, 100, 1 };
            int[] puzzle2 = new int[] { 1, 100, 106, 106, 3, 101, 2, 107, 102, 106, 1, 100, 2, 107, 107, 100, 1 };
            int puzzle_len1 = 17;
            int puzzle_len2 = 17;
            int puzzle_num1 = 0;
            int puzzle_num2 = 1;
            int num_type1 = 0;
            int num_type2 = 0;
            N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
            Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), 1);
        }
        //括号嵌套测试+乘法顺序变换
        [TestMethod()]//测试 (1+(1+2)*(3-2))+1 与 1+((1+2)*(3-2))+1) 是否重复,预期结果为重复 -1
        public void CheckTest8()
        {
            N_Puzzle.labels[0] = 0;
            N_Puzzle.labels[1] = 0;
            int[] puzzle1 = new int[] { 106, 1, 100, 106, 1, 100, 2, 107, 102, 106, 3, 101, 2, 107, 107, 100, 1 };
            int[] puzzle2 = new int[] { 1, 100, 106, 106, 1, 100, 2, 107, 102, 106, 3, 101, 2, 107, 107, 100, 1 };
            int puzzle_len1 = 17;
            int puzzle_len2 = 17;
            int puzzle_num1 = 0;
            int puzzle_num2 = 1;
            int num_type1 = 0;
            int num_type2 = 0;
            N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
            Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), -1);
        }
        //**乘方测试
        [TestMethod()]//测试1+2**3**1与2**3**1 + 1是否重复,预期结果为重复 -1
        public void CheckTest9()
        {
            N_Puzzle.labels[0] = 0;
            N_Puzzle.labels[1] = 0;
            int[] puzzle1 = new int[] { 1, 100, 2, 105, 3, 105, 1 };
            int[] puzzle2 = new int[] { 2, 105, 3, 105, 1, 100, 1 };
            int puzzle_len1 = 7;
            int puzzle_len2 = 7;
            int puzzle_num1 = 0;
            int puzzle_num2 = 1;
            int num_type1 = 0;
            int num_type2 = 0;
            N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
            Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), -1);
        }
        //分数的加法+括号测试
        [TestMethod()]//测试 1/2 + (1/3 + 1/4) 与 1/4 + 1/3 + 1/2 是否重复,预期结果为重复 -1
        public void CheckTest10()
        {
            N_Puzzle.labels[0] = 1;
            N_Puzzle.labels[1] = 1;
            int[] puzzle1 = new int[] { 1, 1030, 2, 100, 106, 1, 1030, 3, 100, 1, 1030, 4, 107 };
            int[] puzzle2 = new int[] { 1, 1030, 4, 100, 1, 1030, 3, 100, 1, 1030, 2 };
            int puzzle_len1 = 13;
            int puzzle_len2 = 11;
            int puzzle_num1 = 0;
            int puzzle_num2 = 1;
            int num_type1 = 1;
            int num_type2 = 1;
            N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
            Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), -1);
        }
        //分数的加法+乘法测试
        [TestMethod()]//测试 1/2 * (1/3 + 1/4) 与 (1/4 + 1/3) * 1/2 是否重复,预期结果为重复 -1
        public void CheckTest11()
        {
            N_Puzzle.labels[0] = 1;
            N_Puzzle.labels[1] = 1;
            int[] puzzle1 = new int[] { 1, 1030, 2, 102, 106, 1, 1030, 3, 100, 1, 1030, 4, 107 };
            int[] puzzle2 = new int[] { 106, 1, 1030, 4, 100, 1, 1030, 3, 107, 102, 1, 1030, 2 };
            int puzzle_len1 = 13;
            int puzzle_len2 = 13;
            int puzzle_num1 = 0;
            int puzzle_num2 = 1;
            int num_type1 = 1;
            int num_type2 = 1;
            N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
            Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), -1);
        }

    //--------------运算符重载测试-------------------------

        [TestMethod()] //分数类 ^ 运算符重载
        public void CheckTest12()
        {
            bool z = false;
            fraction A = new fraction (2,3);
            fraction B = new fraction (0, 1);
            fraction C = A ^ B;
            fraction E = new fraction(1, 1);
            if (C == E) z = true;
            Assert.IsTrue(z);
        }

        [TestMethod()] //分数类 * 运算符重载
        public void CheckTest13()
        {
            bool z = false;
            fraction A = new fraction(2, 3);
            fraction B = new fraction(2, 3);
            fraction C = A * B;
            fraction E = new fraction(4, 9);
            if (C == E) z = true;
            Assert.IsTrue(z);
        }
        [TestMethod()] //分数类 / 运算符重载
        public void CheckTest14()
        {
            bool z = false;
            fraction A = new fraction(2, 3);
            fraction B = new fraction(1, 3);
            fraction C = A / B;
            fraction E = new fraction(2, 1);
            if (C == E) z = true;
            Assert.IsTrue(z);
        }
        [TestMethod()] //分数类 + 运算符重载
        public void CheckTest15()
        {
            bool z = false;
            fraction A = new fraction(2, 3);
            fraction B = new fraction(1, 1);
            fraction C = A + B;
            fraction E = new fraction(5, 3);
            if (C == E) z = true;
            Assert.IsTrue(z);
        }
        [TestMethod()] //分数类 - 运算符重载
        public void CheckTest16()
        {
            bool z = false;
            fraction A = new fraction(2, 3);
            fraction B = new fraction(1, 3);
            fraction C = A - B;
            fraction E = new fraction(1, 3);
            if (C == E) z = true;
            Assert.IsTrue(z);
        }

    } 
}

4、测试结果
在这里插入图片描述

七、C#版实现效果

欢迎主界面
在这里插入图片描述
操作请求界面
在这里插入图片描述
答题界面
在这里插入图片描述
记录查询界面
在这里插入图片描述

八、结队项目开发总结

这次结队项目的开发让我认识到了队友的重要性。还记得以前也有一次结队项目,7个人一起完成,但是由于彼此不熟悉,交流不到位,导致任务工作分配不合理,最后对接时,漏洞百出。这一点正好映证了“增加开发人员,不一定能加快项目开发速度。”这一句话。而这一次因为只有两个人,且彼此熟悉,交流起来也就很容易了,所以整体的项目开发还算比较顺利。但是由于时间紧张,程序实现的功能比较简单,如果时间充裕的话,相信我们还能将程序优化得更好。另外,吸取上一次个人项目的开发经验和教训,这一次采用了类的思想,代码组织也更加有序。在实现过程中,我们也遇到了很多问题,主要是初始值被覆盖,访问地址越界等,好在经过人工检测和代码调试最后改正了过来。对于扩展功能的实现,由于没有什么基础,所以在开发过程中学习到了很多新的知识。此外,程序的一个不足之处是检查用户输入答案时,我们限制了结果的表示形式,也许还可以改进一下,允许用户灵活输入。总的来说,有了上一次个人项目的经验,这一次要顺手多了,毕竟熟能生巧。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值