2019软件工程第三次作业 前言 PSP表格 解题思路历程 单元测试 性能分析与优化瓶颈 心路历程与收获

又开始第三次的软工作业了,说实话的,内心是复杂的。看到如此之长的博客作业,我。。。不过该来的总是会来,该面对的总还是要面对的。安下心来,静静地看着作业要求,开始了我第三次的作业之旅。

<font face="黑体"size=5>Github项目地址:https://github.com/zlrong-hub/software_test/tree/master/031702313

PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(时) 实际耗时(时)
Planning 计划 2h 2h
Estimata 估计这个任务需要所长时间 26h 32h
Development 开发 20h 24h
Analysis 需求分析(包括学习新技术) 3h 4h
Design Spec 生成设计文档 1.5h 2h
Design Review 设计复审 0.5h 1h
Coding Standard 代码规范(为目前的开发制定合适的规范) 1h 1h
Design 具体设计 2h 3h
Coding 具体编码 7h 6h
Code Review 代码复审 3h 3h
Test 测试(自我测试、修改代码、提交修改) 2h 4h
Reporting 报告 2h 3h
Test Report 测试报告 1h 2h
Size Measurement 计算工作量 1h 1h
Postmortem & Process Improvement Plan 事后总结,并提出过程改进计划 2h 2h
合计 28h 34h

解题思路历程

<font face="黑体"size=4>刚拿到这个题目看完题目要求 ,一下子懵了,不知道该怎么下手,平时自己做数独都是用排除法做,思考了一下,貌似计算机上是不能实现的。所以停滞了挺长时间后,还是鼓起勇气,盘它。开始试着去往深了思考,既然决定用C++来写代码,那么首先就要创建一个类。emmm,那就建个名叫Sudoku类吧,每个数独都有它的长度和宽度,它是nn的,则它的边长为n,用一个二维数组s[n][n]来表示每个小格中的数字。

class sudoku
{
private:
	int n;
	int s[10][10];
public:
	sudoku()
	{
		n = 0;
		for (int i = 0; i < 10; i++)
			for (int j = 0; j < 10; j++)
				s[i][j] = 0;
	}
        void sudoku_set(int m, int w[10][10]);
        void output();//sudoku output
	bool check1(int m, int key);
	bool check2(int m, int key);
	int f(int m);
};

<font face="黑体"size=4>而后要解决的函数就作为public部分的方法。那么到底该怎么解决呢。首先,无论是几宫格在填充数字的位置要保证它所在的横纵列不能有重复的数字,于是我写下了check1函数,具体如下:

bool sudoku::check1(int m, int key) //judge line and row
{
       for (int i = 0; i < n; i++) //judge line
	{
		int j = m / n;
		if (s[j][i] == key) return false;
	}
	for (int i = 0; i < n; i++) //judge row
	{
		int j = m % n;
		if (s[i][j] == key) return false;
	}
	return true;
}

<font face="黑体"size=4>接着我们就要另外再考虑四宫格、六宫格、八宫格和九宫格的每一小宫格不能出现重复数字,因此就根据画图标识找规律找出每个小宫格左上角坐标的表示方法,在对该小宫格进行check保证不能出现重复数字,于是就有了check2函数,具体如下:

bool sudoku::check2(int m, int key)//judge small palace
{
	int x, y;
	if (n == 9)
	{
		x = m / 9 / 3 * 3;
		y = m % 9 / 3 * 3;
		for (int i = x; i < x + 3; i++)
			for (int j = y; j < y + 3; j++)
				if (s[i][j] == key) return false;
	}
	else if (n == 8)
	{
		x = m / 8 / 2 * 2;//small sudoku's x
		y = m % 8 / 2 * 2;//small sudoku's y
		for (int i = x; i < x + 4; i++)
			for (int j = y; j < y + 2; j++)
				if (s[i][j] == key) return false;
	}
	else if (n == 6)
	{
		x = m / 6 / 2 * 2;
		y = m % 6 / 3 * 3;
		for (int i = x; i < x + 2; i++)
			for (int j = y; j < y + 3; j++)
				if (s[i][j] == key) return false;
	}
	else {
		x = m / 4 / 2 * 2;
		y = m % 4 / 2 * 2;
		for (int i = x; i < x + 2; i++)
			for (int j = y; j < y + 2; j++)
				if (s[i][j] == key) return false;
	}
	return true;
}

<font face="黑体"size=4>那么问题来了,我们要怎么让它去深度搜索填充呢。在网上查找资料,了解到要用到回溯算法。于是我就开始学习回溯算法的思想和其代码,并生成了f函数,具体如下:

int sudoku::f(int m)
{
	if (m >= n * n) {
		sign = true;
		return 0;
	}
	if (s[m / n][m%n] != 0) f(m + 1);
	else {
		for (int i = 1; i <= n; i++)
		{
			if (n == 3 || n == 5 || n == 7)
			{
				if (check1(m, i) == true)
				{
					s[m / n][m%n] = i;
					f(m + 1);
					if (sign == true) return 0;
					s[m / n][m%n] = 0;
				}
			}
			else
			{
				if (check1(m, i) == true && check2(m, i) == true)
				{
					s[m / n][m%n] = i;
					f(m + 1);
					if (sign == true) return 0;
					s[m / n][m%n] = 0;
				}
			}
		}
	}
	return 0;
}

<font face="黑体"size=4>"最后就是main函数了。定义一个sudoku类数组p[]作为全局变量,再利用for循环调用p[i].f()并依次输出结果。

int main(int argc, char *argv[])
{
	int m=0, x=0;
	FILE *fp;
	if (argc > 1)
	{
		m = atoi(argv[2]);
		x = atoi(argv[4]);
	}
	fp = fopen("input.txt", "r");
	if (fp == NULL) return 1;
	for (int i = 0; i < x; i++)
	{
		sign = false;
		int j = 0, k = 0;
		while (j != m)
		{
			fscanf(fp, "%d", &w[j][k]);
			k++;
			if (k == m)
			{
				k = 0;
				j++;
			}
		}
		p[i].sudoku_set(m, w);
		p[i].f(0);
	}
	fclose(fp);
	fp = fopen("output.txt", "w");
	if (fp == NULL) return 1;
	for (int i = 0; i < x; i++)
	{
		p[i].output();
		for (int j = 0; j < m; j++)
		{
			for (int k = 0; k < m; k++)
				fprintf(fp, "%d ", w[j][k]);
			fprintf(fp, "
");
		}
		fprintf(fp, "
");
	}
	fclose(fp);
	return 0;
}

<font face="黑体"size=4>在主函数中遇到的两个难题:一是对于命令行参数的使用,由于之前没有接触过,所以一时不知道该怎么搞,就去找资料,才知道要在main函数中加入参数,再将命令行中的参数利用atoi()函数将字符串转换为整型变量为后面所用;二呢就是对于如何把input.txt中的数据存放到程序的多个数组中和如何在将数组中的数据在输出到output.txt文件中。几乎这个问题让我纠结了好久,后来慢慢地也就尝试着参考资料解决此问题。对于输入,先利用fopen函数打开文件,然后利用三重循环将文件中的数据利用fscanf函数存放到数组中;对于输出,也是先利用fopen函数打开output文件,然后利用循环语句将数组中的数据利用fprintf函数输出到文件中。当然在将数据存放到对象的数组中时,不能直接访问对象的私有成员s[][],所以需要定义一个全局变量整型数组w[][],先存放到w[][]中,再利用对象的public成员函数sudoku_set()将w[][]中的数据传递到s[][]中,输出也是一样,这里就不再说明了。

单元测试

<font face="黑体"size=4>1.采用白盒测试
<img src="https://img2018.cnblogs.com/blog/1797297/201909/1797297-20190924174543880-879735323.jpg"width="80%"height="80%"/>

性能分析与优化瓶颈

<img src="https://img2018.cnblogs.com/blog/1797297/201909/1797297-20190924181242069-1814302170.jpg"width="80%"height="80%"/>
<img src="https://img2018.cnblogs.com/blog/1797297/201909/1797297-20190924181403469-564014155.jpg"width="80%"height="80%"/>
<font face="黑体"size=4>这是在vs上的性能分析图,因为没有看出来具体类方法的性能分析,所以还在找其他方法。
<font face="黑体"size=4>优化改进:在类方法中的传递属性参数是没有必要再另外定义一个全局变量数组,直接可以调用fopen()和fscanf()进行输入,输出同样。对于f()中存在很多冗余的代码,可以找些规律用几个变量把那几种情况共同表示,让代码可以精简些。

心路历程与收获

<font face="黑体"size=4>之前一直认为做项目打代码是最难也是最重要的部分,但是通过这次做项目才知道需求分析和测试报告也是非常重要的一部分。在做项目之前要先有计划做好PSP部分的预估时间,为界定来规划自己的进度。也学会了命令行参数的传递和读写文件,vs的一些操作比如性能分析(因为以前用的是Dev c++),GitHub仓库的创建、文件夹的创建以及上传文件等等。也通过自己实践,知道了单元测试不仅仅是之前的用例分析还有自己编码来进行自动测试,对每个模块进行全覆盖的测试。但是单元测试部分到现在还是看不懂C++的测试代码,不知道怎么编码。当然也不能只因为这次作业结束就不去再继续学习,还是得完成没做完的part。