软件工程第二次作业

第二次作业个人项目实战


github 代码地址


泛泛而谈的理解

我刚开始打代码的时候觉得打完就好,能过样例就ok。经历过一段时间后会发现有可能样例过了其他测试点全错,所以就会开始多测试几组数据,希望自己的代码能够尽量准确。当准确性开始有保障后,我就会去思考程序本身是不是可以进一步改进,使代码运行速度变的更快。在我看来自己出数据测试就相当于书中说的单元测试,回归测试。不过这种测试变得更加的规范化。而自己对代码的不断改进的过程则相当于书中的效能分析,不过通过效能分析工具使我们对代码有更细致的了解,从而是我们能快速的发现代码中的瓶颈从而改进。

关于个人软件开发流程(psp)这个东西我是第一次了解到。通过书中的大学生和工程师的psp的对比。我发现工作越久,需求分析以及测试花费的时间会越来越多。所以我想我们应该更注重这些方面。而不只是专注于具体的编码。


遇到的困难及解决方法

看到这个题目,第一反应就是搜索。接着我就去网上搜索了一些有关数独的资料,发现构造数独基本上就是两种方法,一个就是深度优先搜索,另一个则是先填中间的宫然后通过置换行列得出所有得宫的数字。我个人觉得深度优先搜索的写法更简洁明了,所以我就用了搜索去写。

整个代码写下来基本上没有什么问题。但是运行后就发现跑的特别慢,用了一分钟左右才跑完。通过性能测试分析,输出函数占用了大部分时间。根据以前的经验知道puts,gets,putchar,getchar这些方法的输入输出会比scanf,printf快很多,所以我就修改了输出函数,使用puts把一整个数独当作一个字符串来进行输出。

代码如下

void generator::print()
{
	int cnt = 0;
	for (int i = 1; i <= 9; i++)
	{
		for (int j = 1; j <= 9; j++)
		{
			ch[cnt++] = shudu[i][j];
			ch[cnt++] = ' ';
		}
		ch[cnt++] = '\n';
	}
	puts(ch);
}

经过修改后的代码需要大概6秒钟的时间可以输出1000000组答案。下图是最终代码的性能测试分析的一张截图,输出函数print只占用了很小的一部分了。

修改后代码的性能分析图,耗时最大的部分就是后面展示的关键代码。


关键代码or设计说明

运行成功的截图,生成了170m大小的sudoku.txt文件

关键代码

for (int i = 1; i <= 9; i++)
{
	if (!c[x][i] && !r[y][i] && !b[box][i])
	{
		c[x][i] = true; r[y][i] = true; b[box][i] = true;
		shudu[x][y] = i + 48;
		get(x, y + 1);
		c[x][i] = false; r[y][i] = false; b[box][i] = false;
	}
}

这是深度搜索中的关键代码,这个搜索函数只有两个参数表示当前在数独的哪个位置。c,r,b三个数组则分别表示的是行,列,宫里面的数字存在情况,例如c[1][1] = true 就表示第一行的数字1已经存在。

对这个代码我进行了单元测试,测试成功了,但是不知什么原因一直代码覆盖率一直为0,显示生成的二进制文件为空,到现在暂时还没有解决...
下面给出单元测试的代码

#include "stdafx.h"
#include "CppUnitTest.h"
#include "E:/软件工程/sudokuproject/sudokuproject/sudokuproject/Generator.h"
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;
using namespace Microsoft::VisualStudio::CppUnitTestFramework;

int res[10][10];
int c[10][10], r[10][10], b[10][10];

namespace UnitTest1
{
TEST_CLASS(UnitTest1)
{
public:
	int getbox(int x, int y)
	{
		int box;
		if (x >= 1 && x <= 3 && y >= 1 && y <= 3) box = 1;
		if (x >= 1 && x <= 3 && y >= 4 && y <= 6) box = 2;
		if (x >= 1 && x <= 3 && y >= 7 && y <= 9) box = 3;
		if (x >= 4 && x <= 6 && y >= 1 && y <= 3) box = 4;
		if (x >= 4 && x <= 6 && y >= 4 && y <= 6) box = 5;
		if (x >= 4 && x <= 6 && y >= 7 && y <= 9) box = 6;
		if (x >= 7 && x <= 9 && y >= 1 && y <= 3) box = 7;
		if (x >= 7 && x <= 9 && y >= 4 && y <= 6) box = 8;
		if (x >= 7 && x <= 9 && y >= 7 && y <= 9) box = 9;

		return box;
	}
	bool check()
	{
		memset(c, 0, sizeof(c));
		memset(r, 0, sizeof(r));
		memset(b, 0, sizeof(b));
		for (int i = 1; i <= 9; i++)
			for (int j = 1; j <= 9; j++)
			{
				c[i][res[i][j]]++;
				r[j][res[i][j]]++;
				b[getbox(i, j)][res[i][j]]++;
			}
		for (int i = 1; i <= 9; i++)
			for (int j = 1; j <= 9; j++)
			{
				if (c[i][j] != 1) return false;
				if (r[i][j] != 1) return false;
				if (b[i][j] != 1) return false;
			}
		return true;
	}
	TEST_METHOD(TestMethod1)
	{
		// TODO: 在此输入测试代码
		freopen("input.txt", "w", stdout);
		generator gen(6);
		int n = 10000;
		gen.getshudu(n);

		fclose(stdout);
		freopen("input.txt", "r", stdin);


		bool pd = true;
		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= 9; j++)
			{
				for (int k = 1; k <= 9; k++)
				{
					scanf("%d", &res[j][k]);
				}
			}
			if (!check()) pd = false;
		}

		Assert::IsTrue(pd);
	}

	TEST_METHOD(TestMethod2)
	{
		// TODO: 在此输入测试代码
		freopen("input.txt", "w", stdout);
		generator gen(6);
		int n = 1000;
		gen.getshudu(n);

		fclose(stdout);
		freopen("input.txt", "r", stdin);


		bool pd = true;
		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= 9; j++)
			{
				for (int k = 1; k <= 9; k++)
				{
					scanf("%d", &res[j][k]);
				}
			}
			if (!check()) pd = false;
		}

		Assert::IsTrue(pd);
	}

};

}

测试成功的截图


psp + 学习进度条更新

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

学习进度条


附加题

附加题我只做了第二个部分,除了本来的要有30个空,以及唯一解的需求外,我还满足了第一部分的每个宫至少有两个空格的要求。
代码的命令行运行方式同基础作业

解题思路:
第一种:通过构造数独的程序生成一部分数独当作该程序的输入,先分别对9个宫,每个宫随机挖3个空。再对整体的数独矩阵挖3个空,达到30个。然后用搜索判断这个数独的唯一性。从30个开始可以每多挖一个空判断一次唯一性,从而一个数独就可以生成多个挖空后的数独。这种方法需要大概17秒左右。

第二种:第二种方法是和同学讨论过后才知道的。当一个数独已经具备唯一解,则这个数独少挖一个空也依然是唯一解,所以我们只需要准备一个数独,随机挖出有40个或者更多的空的数独,并且保证具有唯一解。然后我们从这些空中选出一些空去掉,就可得到不同的数独。因为C(40,9)= 273438880 远远超过了1000000。所以很容易就可以得出所有结果。因为满足了每个宫最少有两个空格的需求,所以我需要7秒,如果不需要满足则2秒多就可以了。

下图为代码运行成功的截图


个人总结

这次的作业,加强了我对vs这个工具的使用,知道了如何进行单元测试,如何进行效能分析。虽然还是有很多不懂的地方,但是比一开始什么都不懂好多了。这次的作业如果只算写代码,以及测试的时间并不是很长,但是花在鼓捣vs这个软件上的时间及其漫长,很多东西都是各种百度,到最后却依然不得其解。并且很多文件资料都是英文,应该多加强这方面的阅读能力。

posted @ 2017-09-10 01:37  Starset  阅读(212)  评论(4编辑  收藏  举报