软件工程实践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

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();
            }
        }

测试结果

·正面测试

软件工程实践2019第三次作业_第1张图片

·边界测试

软件工程实践2019第三次作业_第2张图片
软件工程实践2019第三次作业_第3张图片

五、性能改进

软件工程实践2019第三次作业_第4张图片
软件工程实践2019第三次作业_第5张图片
软件工程实践2019第三次作业_第6张图片
软件工程实践2019第三次作业_第7张图片

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

六、代码说明

主要思路流程如下:
软件工程实践2019第三次作业_第8张图片

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

程序使用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();
          }

软件工程实践2019第三次作业_第9张图片

·输入文本为空

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

软件工程实践2019第三次作业_第10张图片

八、心得体会

   这次作业让我学到了很多,首先是文本的读写与命令行的使用,在这块上我耗了很长的时间,其次是一些插件的使用,比如jprofile、checkstyle和juint,关于单元检测里juint的使用,还不是很熟练,但相信以后会慢慢熟悉的。在单元检测这块内容时候,也懵了很久,自己的方法都是相互调用的,怎么进行检测?在仔细看完构建之法后,终于知道单元检测就是把模块提取出来,可以自己加上输入输出语句,进行检测,然后在保证某些模块接口稳定的情况下,借助它对其他接口模块进行单元测试。总之就是,累与学无止境。

你可能感兴趣的:(软件工程实践2019第三次作业)