第二次作业个人项目实战
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++] = '
';
}
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这个软件上的时间及其漫长,很多东西都是各种百度,到最后却依然不得其解。并且很多文件资料都是英文,应该多加强这方面的阅读能力。