• 软件工程实践2019第三次作业


    一、GitHub连接

    传送门

    二、PSP表格

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

    三、思路描述

        一开始看到数独,并没有考虑很多,用着半生不熟的JAVA语言直接上手。因为题目要求是三到九宫格,就打算以不变应万变,从最麻烦的九宫格入手,其他的就在九宫格的基础上改改。结果一个宫格一个类,一共写了八个类!自己看着代码都头昏。但是自己思考一个快捷的算法又有点困难。于是我就想,数独算法应该比较成熟,网上或许有很多资料可以查。然鹅,数独的生成算法确实挺多,求解算法看起来很高端,模拟人类求解数独,采用区块摒除法、数组法、四角对角线、唯一矩形、全双值坟墓、单数链、异数链及其他数链的高级技巧等等。我百度完这些方法后,满脑子都是大写的懵逼。万分绝望下我拿起数据结构的书翻了翻,入眼就是“回溯法解决八皇后问题”,而里面的第一行介绍就是,可以利用回溯法解决数独问题。我仔细看完八皇后的求解,总觉得思路和一开始写的有一点点相似,都是设置一个A数组,初始化为-1,再一一检测后置0,但是到了结尾,文章提供了递归法检验数组求解是否正确的方法,我就寻思着,能不能把它改改,编写成求解数组的方法呢?于是就有了现在的程序。具体思路在代码说明部分。
        再写完程序后,我又找到一个关键词,舞蹈链,据说这是个更快捷的方法求解数独,但是苦于没有时间研究,只能作罢。
    

    四、单元测试

    1、测试读取文件方法

    BufferedReader br = new BufferedReader(new FileReader("D:\test.txt"));//构造一个BufferedReader类来读取文件
     String s = null;
     int[][] chess = new int[9][9];
     
     for(int k = 0; k < chess.length ;k++ )
     for(int j =0; j < chess[k].length; j ++)
     {
     chess[k][j] = 0;
     }
     
     int i = 0;
     while((s = br.readLine())!=null){//使用readLine方法,一次读一行
       String[] temp = s.split(" ");
       System.out.println(temp[2]);
       for(int j=0;j<temp.length;j++) {
       System.out.println(temp[j]);
       chess[i][j]=Integer.parseInt(temp[j]);
       
       }i++;
      
     }
     System.out.println(chess.toString());
     for(int k = 0; k < chess.length ;k++ ){
     for(int j =0; j < chess[k].length; j ++)
     {
     System.out.print(chess[k][j]+ " ");
     }
     System.out.println();
     }
     
    

    2、测试递归方法solve()

    @Test
    public boolean solve(int[][] chess, int i, int j, int m) {  //同一行按列搜,若j=9,则跳到下一行搜,若宫格填完返回true
    
    		if (j == m) {
    			if (i == m - 1)
    				return true;
    			i++;
    			j = 0;
    		}
    
    		if (chess[i][j] != 0) {
    			return solve(chess, i, j + 1, m);
    		}
    
    		for (char k = 1; k <= m; k++) {
    			if (isValid(chess, i, j, k, m)) {
    				chess[i][j] = k;
    				if (solve(chess, i, j + 1, m))
    					return true;
    				else
    					chess[i][j] = 0;
    			}
    		}
    		return false;
    	}
    

    3、测试写入文本方法printFuntxt()

    @Test
    private static void printFuntxt(int[][] totalchess, int n, int m, String fileout) {
    
        	File file=new File(fileout);
        	try (PrintWriter output = new PrintWriter(file)) {
    
        		for (int i = 0; i < m * n; i++) {
    
        			if(i % m == 0 && i != 0)
                    	output.println();
    
                    for (int j = 0; j < m; j++) {
                    	if (totalchess[i][j] == 0) {
                    		System.out.println("无解");
                    		return;
                    	}
                    	if (j != m-1)
                    		output.print(totalchess[i][j] + " ");
                    	else if(i!= m * n - 1)
                    		output.print(totalchess[i][j] + "
    ");
                    	else
                    		output.print(totalchess[i][j]);
                    }
        		}
    		}
        		catch (FileNotFoundException e) {
    			e.printStackTrace();
        		}
        	}
    

    测试结果

    ·正面测试

    正面测试

    ·边界测试

    边界测试
    边界测试

    五、性能改进

    性能分析
    性能分析
    性能分析
    性能分析

    虽然看不太懂性能分析图,但是还是可以看得出,开销最大的是读取文件的函数。第一个版本经过改进后,Classes的开支明显减少。

    六、代码说明

    主要思路流程如下:
    流程图

    其中最核心的就是递归和判断宫格是否可以填充。

    程序使用solve()方法来进行递归。
    先从第i行开始,检测第j个位置是否可以填k值,若不行,则j+1,按照j=0,1……m-1的次序。

    ·如果可以填,则将这个格子置为k,判断下一个格子。如果j=m-1,则跳到下一行继续判断。

    ·如果不可以填,则将这个位置置0。

    ·如果所有格子都填完了,则返回true。

    public boolean solve(int[][] chess, int i, int j, int m) {  //同一行按列搜,若j=9,则跳到下一行搜,若宫格填完返回true
    
    		if (j == m) {
    			if (i == m - 1)
    				return true;
    			i++;
    			j = 0;
    		}
    
    		if (chess[i][j] != 0) {
    			return solve(chess, i, j + 1, m);
    		}
    
    		for (char k = 1; k <= m; k++) {
    			if (isValid(chess, i, j, k, m)) {
    				chess[i][j] = k;
    				if (solve(chess, i, j + 1, m))
    					return true;
    				else
    					chess[i][j] = 0;
    			}
    		}
    		return false;
    	}
    

    接着用isvalid()方法判断这个位置是否可以填值。
    在检测第j个位置是否可以填k值时,分别按行、列、某些阶数的宫图还需要按宫进行判断,若行数不为零且已经填了k值时,则返回false,k值加1,进行下一轮的判断。

    public boolean isValid(int[][] chess, int i, int j, char c,int m) {  //有效空格
    
    		for (int k = 0; k < m; k++) {
    
    			if (chess[i][k] != 0 && chess[i][k] == c)   //按行搜
    				return false;
    
    			if (chess[k][j] != 0 && chess[k][j] == c)   //按列搜
    				return false;
    
    			if (m == 4) {   //按宫搜
    				if (chess[i / 2 * 2 + k / 2][j / 2 * 2 + k % 2] != 0 
    						&& chess[i / 2 * 2 + k / 2][j / 2 * 2 + k % 2] == c)
    					return false;
    			} else if(m == 6) {
    				if (chess[i / 2 * 2 + k / 3][j / 3 * 3 + k % 3] != 0 
    						&& chess[i / 2 * 2 + k / 3][j / 3 * 3 + k % 3] == c)
    					return false;
    			} else if(m == 8) {
    				if (chess[i / 4 * 4 + k / 2][j / 2 * 2 + k % 2] != 0 
    						&& chess[i / 4 * 4 + k / 2][j / 2 * 2 + k % 2] == c)
    					return false;
    			} else if(m == 9) {
    				if (chess[i / 3 * 3 + k / 3][j / 3 * 3 + k % 3] != 0 
    						&& chess[i / 3 * 3 + k / 3][j / 3 * 3 + k % 3] == c)
    						return false;
    			}
    		}
    		return true;
    	}
    

    关于文件的读取,是将文件按行读取,存入一个叫totalchess的二维数组中。

    public static int[][] readFile(File file, int[][] totalchess) {     //把文件读入一个大数组
    		  String s = null;
    		  for (int k = 0; k < totalchess.length; k++) {
    				 for (int j = 0; j < totalchess[k].length; j++) {
    					 totalchess[k][j] = 0;
    				 }
    		  }
    		  try {
    			  int i = 0;
    			  BufferedReader br = new BufferedReader(new FileReader(file)); //构造一个BufferedReader类来读取文件
    			  while ((s = br.readLine()) != null) { //使用readLine方法,一次读一行
    				  if (s.equals(""));
    				  else {
    					  String[] temp = s.split(" ");
    					  for (int j = 0; j < temp.length; j++) {
    						  totalchess[i][j] = Integer.parseInt(temp[j]);
    				  	}
    					  i++;
    				  }
    			  }
    			 br.close();
    		  } catch (Exception e) {
    		   e.printStackTrace();
    		  }
    		  return totalchess;
      }
    

    而在solveSudoku方法中,如果solve方法返回值为true,即宫格已经填写完毕,将填写完成的chess数组存入totalchess数组中一边最后一次性写入文本。

    public void solveSudoku(int[][] chess, int m, int[][]totalchess) {
    
    		if (chess == null || chess.length != m || chess[0].length != m) {
    			System.out.println("error!");
    			return;
    		}
    
    		if (solve(chess, 0, 0, m)) {   // 打印结果
    			for (int i = 0; i < m; i++) {
    				for (int j = 0; j < m; j++) {
    					totalchess[y][j] = chess[i][j];
    					//System.out .print(totalchess[y][j]);
    				}
    				//System.out .println();
    				y++;
    			}
    		}
    	}
    

    最后是把totalchess数组写入文本。

     private static void printFuntxt(int[][] totalchess, int n, int m, String fileout) {
    
        	File file=new File(fileout);
        	try (PrintWriter output = new PrintWriter(file)) {
    
        		for (int i = 0; i < m * n; i++) {
    
        			if(i % m == 0 && i != 0)
                    	output.println();
    
                    for (int j = 0; j < m; j++) {
                    	if (totalchess[i][j] == 0) {
                    		System.out.println("无解");
                    		return;
                    	}
                    	if (j != m-1)
                    		output.print(totalchess[i][j] + " ");
                    	else if(i!= m * n - 1)
                    		output.print(totalchess[i][j] + "
    ");
                    	else
                    		output.print(totalchess[i][j]);
                    }
        		}
    		}
        		catch (FileNotFoundException e) {
    			e.printStackTrace();
        		}
        	}
    

    这与第一个版本相比,最大的优点就是能将七个阶级的宫根据if语句在一个方法中进行实现,而不需要创建七个类,代码冗长繁琐。

    七、异常处理

    ·命令行输入格式不正确

    if (args.length != 8) {
    			System.out.println("Invalid input.");
    			System.exit(0);
    		}
    

    命令行输入格式不正确

    ·文本输入数组越界

     try {
    			  int i = 0;
    			  BufferedReader br = new BufferedReader(new FileReader(file)); //构造一个BufferedReader类来读取文件
    			  while ((s = br.readLine()) != null) { //使用readLine方法,一次读一行
    				  if (s.equals(""));
    				  else {
    					  String[] temp = s.split(" ");
    					  for (int j = 0; j < temp.length; j++) {
    						  totalchess[i][j] = Integer.parseInt(temp[j]);
    				  	}
    					  i++;
    				  }
    			  }
    			 br.close();
    		  } catch (Exception e) {
    		   e.printStackTrace();
    		  }
    

    文本输入数组越界

    ·输入文本为空

    if ((filein == null) || !filein.exists() || filein.length() == 0) {
    			System.out.println("输入文件不存在");
    			System.exit(1);
    		}
    

    边界测试

    八、心得体会

       这次作业让我学到了很多,首先是文本的读写与命令行的使用,在这块上我耗了很长的时间,其次是一些插件的使用,比如jprofile、checkstyle和juint,关于单元检测里juint的使用,还不是很熟练,但相信以后会慢慢熟悉的。在单元检测这块内容时候,也懵了很久,自己的方法都是相互调用的,怎么进行检测?在仔细看完构建之法后,终于知道单元检测就是把模块提取出来,可以自己加上输入输出语句,进行检测,然后在保证某些模块接口稳定的情况下,借助它对其他接口模块进行单元测试。总之就是,累与学无止境。
  • 相关阅读:
    台湾9大知名开源社区
    使用SignalR打造消息总线
    ENode 2.0
    Wireshark基本介绍和学习TCP三次握手 专题
    linux tomcat 的安装
    linux 之静默安装oracle
    什么是全栈呢(转)
    Android开发自学笔记(基于Android Studio1.3.1)—1.环境搭建(转)
    hdu 4919 Exclusive or
    D
  • 原文地址:https://www.cnblogs.com/lx2509/p/11583817.html
Copyright © 2020-2023  润新知