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

一、GitHub连接

传送门

二、PSP表格

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

三、思路描述

    一开始看到数独,并没有考虑很多,用着半生不熟的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] + "\n");
                    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] + "\n");
                    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的使用,还不是很熟练,但相信以后会慢慢熟悉的。在单元检测这块内容时候,也懵了很久,自己的方法都是相互调用的,怎么进行检测?在仔细看完构建之法后,终于知道单元检测就是把模块提取出来,可以自己加上输入输出语句,进行检测,然后在保证某些模块接口稳定的情况下,借助它对其他接口模块进行单元测试。总之就是,累与学无止境。

转载于:https://www.cnblogs.com/lx2509/p/11583817.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值