• 软件工程实践第二次作业——个人项目实战(数独)


    作业链接

    1)Github项目地址

    2)在开始实现程序之前,在下述PSP表格记录下你估计将在程序的各个模块的开发上耗费的时间
    见 8)。
    3)解题思路描述

    拿到题目后,阅读了项目需求,得知这次作业要求我们利用程序随机构造出N个已解答的数独棋盘。项目的关键就是一要构建出9 * 9且符合要求的数独棋盘。由于自己并未写过有关构建数独的算法,于是就去上网搜了相关资料,我选择了比较容易理解的置换法。置换法的思路是:数独棋盘的9宫从左至右,从上至下标记为M1,M2,...,M8,M9(方便理解和阐述),先构建3 * 3的初始棋盘将其放在M5的位置上,将M5进行行变换生成M4,M6,将M5进行列变换生成M2,M8。现在再分别以M2,M8,进行行变换,分别得到M1,M3和M7,M9。构建好之后,将数独棋盘输出至txt,采用了ofstream方法。

    4)设计实现过程

    理清了思路,接着就要设计实现了。代码包含了generator类,在generator类中声明了以下函数

    • int get_random_number(); //获取随机数1~9
    • void generate_sudoku(); //构建数独
    • void generate_txt(int num,string str); //将数独棋盘写入文件

    5)代码说明

    以下是构造数独棋盘的代码:

    int begin_board[3][3];  //数独棋盘中心3*3棋盘(基点棋盘)
    
    int sudoku_board[10][10];
    //数独棋盘中心9*9棋盘(9*9棋盘分成9个3*3棋盘填充,从左往右,从上到下记为M1,M2, ... ,M8,M9棋盘)
    
    int flag[10]; //判断随机数是否重复
    
    
    void generator::generate_sudoku() //构建数独
    {
    
    memset(begin_board, 0, sizeof(begin_board));
    memset(flag, 0, sizeof(flag));
    memset(sudoku_board, 0, sizeof(sudoku_board));
    
    /*
     生成基点3*3棋盘
    */
    
    int count = 0;
    
    for (int i = 0; i < 3; i++)
    	for (int j = 0; ; j++)
    	{
    		int temp = get_random_number();
    		//cout << temp << endl;
    		if (flag[temp] == 0)
    		{
    			begin_board[i][count] = temp;
    			flag[temp] = 1;
    			count++;
    			if (count == 3)
    			{
    				count = 0;
    				break;
    			}
    		}
    	}
    
    
    
    for (int i = 0; i < 3; i++)
    	for (int j = 0; j < 3; j++)
    	{
    		sudoku_board[i + 3][j + 3] = begin_board[i][j];  // 生成9*9棋盘的中心的3*3棋盘(M5)
    
    		/*
    		 以基点棋盘进行行变换,向左,右扩展,生成M4,M6
    		*/
    
    		switch (i)
    		{
    		case 0:
    			sudoku_board[i + 4][j] = begin_board[i][j];  // 填充9*9棋盘的[4][0~2]位置
    			sudoku_board[i + 5][j + 6] = begin_board[i][j];  // 填充9*9棋盘的[5][6~8]位置
    			break;
    
    		case 1:
    			sudoku_board[i + 4][j] = begin_board[i][j];  // 填充9*9棋盘的[5][0~2]位置
    			sudoku_board[i + 2][j + 6] = begin_board[i][j];  // 填充9*9棋盘的[3][6~8]位置
    			break;
    
    		case 2:
    			sudoku_board[i + 1][j] = begin_board[i][j];  // 填充9*9棋盘的[3][0~2]位置
    			sudoku_board[i + 2][j + 6] = begin_board[i][j];  // 填充9*9棋盘的[4][6~8]位置
    			break;
    		}
    
    		/*
    		  以基点棋盘进行列变换,向上,向下扩展,生成M2,M8
    		*/
    
    		switch (j)
    		{
    		case 0:
    			sudoku_board[i][j + 4] = begin_board[i][j];  // 填充9*9棋盘的[0~2][4]位置
    			sudoku_board[i + 6][j + 5] = begin_board[i][j];  // 填充9*9棋盘的[6~8][5]位置
    			break;
    
    		case 1:
    			sudoku_board[i][j + 4] = begin_board[i][j];  // 填充9*9棋盘的[0~2][5]位置
    			sudoku_board[i + 6][j + 2] = begin_board[i][j];  // 填充9*9棋盘的[6~8][3]位置
    			break;
    
    		case 2:
    			sudoku_board[i][j + 1] = begin_board[i][j];  // 填充9*9棋盘的[0~2][3]位置
    			sudoku_board[i + 6][j + 2] = begin_board[i][j];  // 填充9*9棋盘的[6~8][4]位置
    			break;
    		}
    	}
    
    /*
    	   以M2作为基点棋盘进行行变换,向左,向右扩展,生成M1,M3
    */
    
    for (int i = 0; i <= 2; i++)
    	for (int j = 3; j <= 5; j++)
    	{
    		switch (i)
    		{
    		case 0:
    			sudoku_board[i + 1][j - 3] = sudoku_board[i][j];  // 填充9*9棋盘的[1][0~2]位置
    			sudoku_board[i + 2][j + 3] = sudoku_board[i][j];  // 填充9*9棋盘的[2][6~8]位置
    			break;
    
    		case 1:
    			sudoku_board[i + 1][j - 3] = sudoku_board[i][j];  // 填充9*9棋盘的[2][0~2]位置
    			sudoku_board[i - 1][j + 3] = sudoku_board[i][j];  // 填充9*9棋盘的[0][6~8]位置
    			break;
    
    		case 2:
    			sudoku_board[i - 2][j - 3] = sudoku_board[i][j];  // 填充9*9棋盘的[0][0~2]位置
    			sudoku_board[i - 1][j + 3] = sudoku_board[i][j];  // 填充9*9棋盘的[1][6~8]位置
    			break;
    		}
    	}
    
    
    /*
    以M8作为基点棋盘进行行变换,向左,向右扩展,生成M7,M9
    */
    
    for (int i = 6; i <= 8; i++)
    	for (int j = 3; j <= 5; j++)
    	{
    		switch (i)
    		{
    		case 6:
    			sudoku_board[i + 1][j - 3] = sudoku_board[i][j];  // 填充9*9棋盘的[7][0~2]位置
    			sudoku_board[i + 2][j + 3] = sudoku_board[i][j];  // 填充9*9棋盘的[9][6~8]位置
    			break;
    
    		case 7:
    			sudoku_board[i + 1][j - 3] = sudoku_board[i][j];  // 填充9*9棋盘的[8][0~2]位置
    			sudoku_board[i - 1][j + 3] = sudoku_board[i][j];  // 填充9*9棋盘的[6][6~8]位置
    			break;
    
    		case 8:
    			sudoku_board[i - 2][j - 3] = sudoku_board[i][j];  // 填充9*9棋盘的[6][0~2]位置
    			sudoku_board[i - 1][j + 3] = sudoku_board[i][j];  // 填充9*9棋盘的[9][6~8]位置
    			break;
    		}
    	}
    
    }
    

    思路的话,前面已经叙述过了,就不再赘述了。

    6)测试运行。
    输入N=10,在控制台得出以下结果:

    以下是命令行测试截图:

    7)记录在改进程序性能上所花费的时间,描述你改进的思路,并展示一张性能分析图,并展示你程序中消耗最大的函数

    第一次使用性能分析工具,用的不是很得心应手...

    以N=1000测试得到以下的性能分析图:

    从性能分析图可得出程序中消耗最大的函数是: void generate_txt(int num,string str); //将数独棋盘写入文件

    改进前代码:

    void generator::generate_txt(string str) //将数独棋盘写入文件
    {
        string str1 = str + "/sudoku.txt";
        //cout << str1 << endl;
    
        ofstream txt(str1, ios::out);
        //txt.open(str1);
    
        for (int i = 0; i < 9; i++)
    	    for (int j = 0; j < 9; j++)
    	    {
    		    txt << sudoku_board[i][j] << " ";
    
    		    if (j == 8) txt << endl;
    	    }
    
        txt << endl;
        txt << endl;
        txt.close();
    }
    

    通过在主函数调用写入txt函数,每写入一次就得打开一次文件,比较耗时

    改进方法:只打开一次文件,直至写入文件完成后关闭文件

    改进后代码:

    void generator::generate_txt(int num, string str) //将数独棋盘写入文件
    {
    generator gene;
    
    string path = str + "/sudoku.txt";
    
    //cout << path << endl;
    
    ofstream  txt(path, ios::out);
    
    for (int k = 0; k < num; k++)
    {
    	gene.generate_sudoku();
    
    	for (int i = 0; i < 9; i++)
    		for (int j = 0; j < 9; j++)
    		{
    			txt << sudoku_board[i][j] << " ";
    
    			if (j == 8) txt << endl;
    		}
    
    	txt << endl;
    	txt << endl;
    }
    
    txt.close();
    }
    

    改进后得到以下的性能分析图:

    好像只快了3秒钟额...虽然提升的不是很多,但自己也挺满足的。

    在网上查了一些资料,了解到c++里的文件流fstream输出效率在批量较大的时候远不如c里的freopen,因此再一次改进输出函数

    void generator::generate_txt(int num, char * str) //将数独棋盘写入文件
     {
    generator gene;
    
    freopen(str, "w", stdout);
    
    for (int k = 0; k < num; k++)
    {
    	int len = 0;
    	gene.generate_sudoku();
    
    	for (int i = 0; i < 9; i++)
    		for (int j = 0; j < 9; j++)
    		{
    			res[len++] = sudoku_board[i][j] + '0' ;
    			res[len++]=' ';
    
    			if (j == 8) res[len++] =' 
    ';
    		}
    
    	puts(res);
    }
    
    fclose(stdout);
     }
    

    改进后得到以下的性能分析图:

    由性能分析图可知,输出写入txt的效率大大提高了。

    8)PSP 2.1表格

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

    9)遇到的困难与收获

    对于确保生成的棋盘文件 sudoku.txt 与可执行文件在同一目录下,生成文件时请使用相对路径这一要求,自己思考了挺久的。
    解决方法:我的想法是去获取.exe的路径,然后在该路径下将数独棋盘写入 sudoku.txt,题目要求我们使用相对路径,然而自己完全没有这个概念,于是就去了解了绝对路径和相对路径的区别;对于获取.exe的路径,运用了GetModuleFileName函数(参考资料1,参考资料2
    收获的话,学习了一些新的知识,如获取.exe的路径...也复习了一些知识,如通过命令行调用.exe...除此之外,通过填写PSP表格,让自己估算完成每一步骤的所时间,督促自己提高效率,在实践的过程中更有目标性。因此在之后的项目实践中要好好利用PSP这一利器。
    至于附加题,想法是用MFC开发GUI界面,自己尝试着去学习,发现并不能很快的学会,因此就先放着...

    积跬步,至千里,未来可期。

  • 相关阅读:
    ContextLoaderListener作用详解
    Spring启动流程
    解决filter拦截request中body内容后,字符流关闭,无法传到controller的问题
    Spring拦截器从Request中获取Json格式的数据
    Filter和Interceptor的终归作用还是从入口修改或验证请求进来的数据
    HttpServletRequest常用获取URL的方法
    Spark1.0.0 监测方法
    nginx代理人server结合tomcat采用
    Guangsoushensou 2
    admob广告开始个人资料网址
  • 原文地址:https://www.cnblogs.com/cjqcjq/p/7473816.html
Copyright © 2020-2023  润新知