软件工程第三次作业

Github地址

PSP表格
Personal Software Process Stages预估耗时实际耗时
Planning计划2小时1小时
Estimate这个任务需要多少时间24小时36小时
Development开发8小时12小时
Analysis需求分析(包括学习新技术)2小时3小时
Design Spec生成设计文档2小时4小时
Design Review设计复审3小时2小时
Coding Standard代码规范1小时3小时
Design具体设计1小时40分钟
Coding具体编码6小时4小时
Code Review代码复审5小时3小时
Test测试(自我测试,修改代码,提交修改)1小时2小时
Reporting报告1小时2小时
Test Repor测试报告1小时2小时
Size Measurement计算工作量1小时 1小时
Postmortem事后总结,并提出过程改进计划2小时 1小时
Improvement Plan过程改进计划2小时 2小时
合计 38小时 40小时40分钟

需求

实现一个命令行程序,不妨称之为Sudoku

百度百科简介:

数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。

具体任务:

现在我们想一步一步来,完成从三宫格到九宫格的进阶;完成三宫格和其他博客任务,就算过了初级考核,其他的算升级。具体各阶规则如下:

1797358-20190921105813183-90158343.png

输入:

输入文件名以命令行参数传入。例如我们在命令行窗口(cmd)中进入Sudoku.java所在文件的目录然后输入:
javac Sudoku.java
java Sudoku -m 9  -n 2  -i input.txt  -o output.txt
-m 宫格阶级(3~9的整数)
-n 待解答盘面数目
-i 指定输入文件(需要在自己的电脑磁盘里面提前创建好)
-o 指定程序的输出文件(也需要在自己的电脑里面提前创建好)

上面语句对应的输入文件如下:

0 0 8 0 0 4 0 0 9
0 6 0 0 0 0 1 0 0
0 3 7 0 0 0 0 0 0
8 0 1 2 6 9 0 0 3
0 2 5 4 7 0 0 6 8
0 9 0 0 0 5 0 0 0
9 0 0 1 5 2 3 7 4
0 4 0 3 9 8 0 1 6
1 5 3 6 4 7 8 0 2

2 0 0 0 0 0 0 0 0
0 0 6 3 0 0 0 7 0
5 0 0 0 0 0 1 0 0
9 6 7 4 0 0 0 0 5 
8 1 3 0 0 0 0 0 0
4 2 0 7 1 8 9 6 3 
3 5 0 0 4 1 6 9 7
6 9 8 2 7 3 5 4 1
0 4 0 0 5 9 2 0 8

输出

输出n个程序解出的盘面,每两个盘面间空一行,每个盘面中,每两个小格之间有一个空格。

上面的命令行对应的输出文件output.txt组织如下:

5 1 8 7 2 4 6 3 9
2 6 9 5 8 3 1 4 7
4 3 7 9 1 6 2 8 5
8 7 1 2 6 9 4 5 3
3 2 5 4 7 1 9 6 8
6 9 4 8 3 5 7 2 1 
9 8 6 1 5 2 3 7 4
7 4 2 3 9 8 5 1 6
1 5 3 6 4 7 8 9 2

2 7 9 1 8 4 3 5 6
1 8 6 3 2 5 4 7 9
5 3 4 9 6 7 1 8 2
9 6 7 4 3 2 8 1 5
8 1 3 5 9 6 7 2 4
4 2 5 7 1 8 9 6 3
3 5 2 8 4 1 6 9 7
6 9 8 2 7 3 5 4 1 
7 4 1 6 5 9 2 3 8

解题思路:

拿到题目的时候其实没有看懂到底要求做什么,对于命令行传入参数也是一无所知,在群里面询问大佬们,了解命令行如何传参之后,才正式开始构思如何求解九宫格盘面,好在自己平时也喜欢玩数独,给我一个九宫格的盘面30分钟不到就能解完,可如今要自己来手写代码,让代码来解读,这到难倒我了,以自己目前的水平和知识面,写完估计的要300分钟吧!废话不多说了,先讲讲自己的思路吧:首先我们得知道3-9宫格最终盘面里每个数字所应满足的要求:
三宫格:盘面是3*3。使1-3每个数字在每一行、每一列中都只出现一次,不考虑宫;
四宫格:盘面是2*2四个宫,每一宫又分为2*2四个小格。使1-4每个数字在每一行、每一列和每一宫中都只出现一次;
五宫格:盘面是5*5。使1-5每个数字在每一行、每一列中都只出现一次,不考虑宫;
六宫格:盘面是2*3六个宫,每一宫又分为3*2六个小格。使1-6每个数字在每一行、每一列和每一宫中都只出现一次;
七宫格:盘面是7*7。使1-7每个数字在每一行、每一列中都只出现一次,不考虑宫;
八宫格:盘面是4*2八个宫,每一宫又分为2*4八个小格。使1-8每个数字在每一行、每一列和每一宫中都只出现一次;
九宫格:盘面是3*3九个宫,每一宫又分为3*3九个小格。使1-9每个数字在每一行、每一列和每一宫中都只出现一次;

根据这个要求写一个方法legal,以判断在九宫格中的坐标(x,y)的位置上插入value,是否符合上述规则,代码如下

    public static Boolean legal(int a[][],int x, int y, int value,int m) {
 
        for (int i = 0; i < m; i++) {
            //如果列中有value,则返回false
            if (i != x && a[i][y] == value) {
                return false;
            }
            //如果行中有value,则返回false
            if (i != y && a[x][i] == value) {
                return false;
            }
        }
        if(m==9){
            //(minX,minY)是(x,y)所属小九宫格的左上角的坐标
            int minX = x / 3 * 3;
            int minY = y / 3 * 3;
     
            for (int i = minX; i < minX + 3; i++) {
                for (int j = minY; j < minY + 3; j++) {
                    //如果小九宫格中的非(x,y)的坐标上的值为value,返回false
                    if (i != x && j != y && a[i][j] == value) {
                        return false;
                    }
                }
            }
        }
        if(m==4){
            //(minX,minY)是(x,y)所属小4宫格的左上角的坐标
            int minX = x / 2 * 2;
            int minY = y / 2 * 2;

            for (int i = minX; i < minX + 2; i++) {
                for (int j = minY; j < minY + 2; j++) {
                    //如果小九宫格中的非(x,y)的坐标上的值为value,返回false
                    if (i != x && j != y && a[i][j] == value) {
                        return false;
                    }
                }
            }
        }
        if(m==8){
            //(minX,minY)是(x,y)所属小8宫格的左上角的坐标
            int minX = x / 4 * 4;
            int minY = y / 2 * 2;
     
            for (int i = minX; i < minX + 4; i++) {
                for (int j = minY; j < minY + 2; j++) {
                    //如果小九宫格中的非(x,y)的坐标上的值为value,返回false
                    if (i != x && j != y && a[i][j] == value) {
                        return false;
                    }
                }
            }
        }
        if(m==6){
            //(minX,minY)是(x,y)所属小6宫格的左上角的坐标
            int minX = x / 2 * 2;
            int minY = y / 3 * 3;
     
            for (int i = minX; i < minX + 2; i++) {
                for (int j = minY; j < minY + 3; j++) {
                    //如果小九宫格中的非(x,y)的坐标上的值为value,返回false
                    if (i != x && j != y && a[i][j] == value) {
                        return false;
                    }
                }
            }
        }
        return true;
    }
legal方法写完之后,并没有结束,求解九宫格的核心思想让我为之思考了一整天,首先想到的是按照平时玩数独的思维来解答:也就是自己常用的排除法,先将每行每列每个宫里面不可能出现的数字排除掉,然后将一些确定的数字填上去,然后再排除,再填......显然这种方法就是没脑子的人才会想的出来的,写完估计都猴年马月了,于是去询问ACM的算法大佬,提示了我一下,让我使用回溯法,刚提完,我瞬间“柳暗花明又一村”,马上有了思路:

具体代码和注释如下:

shuDu[][]是用来存放数独游戏的二维数组。

public static int shuDu[][] = new int[9][9];
    public static void setShuDu(int[][] shuDu) {
        Sudu.shuDu = shuDu;
    }

使用回溯法求解数独

    public static void shuDu_solution(int k,int m) throws IOException {
        if (k == (m*m)) {
          String src= "D:\\sudoku\\"+outputFilename;
            try{
            FileWriter fw = new FileWriter(src,true);
            for(int i=0;i<m;i++){
                for(int j=0;j<m;j++){ 
                    fw.write(shuDu[i][j]+" ");

                }

                fw.write("\r\n");
            }
            fw.write("\r\n");
            fw.close(); // 最后记得关闭文件  
            }
            catch (Exception e) {  
                e.printStackTrace();  
            }  
            return;
        }
        int x = k / m;
        int y = k % m;
        if (shuDu[x][y] == 0) {
            for (int i = 1; i <= m; i++) {
                shuDu[x][y] = i;
                if (legal(shuDu,x, y, i,m)) {
                    shuDu_solution(k + 1,m);
                }
            }
            shuDu[x][y] = 0;
        } else {
            shuDu_solution(k + 1,m);
        }
    }

初始化命令行的传入的参数

    public static void loadArgs(String args[]){
        if(args.length>0&&args!=null){
            for(int i=0;i<args.length;i++){
                switch (args[i]) {
                case "-i":
                    inputFilename = args[++i];
                    break;
                case "-o": 
                    outputFilename = args[++i];
                    break;
                case "-m": 
                    m=Integer.valueOf(args[++i]);
                    break;
                case "-n":
                    n=Integer.valueOf(args[++i]);
                    break;

                default:
                    break;
                }
            }
        }
    }

最后就是主函数

1797358-20190921120441780-648281688.png

 public static void main(String[] args) throws IOException {
        loadArgs(args);
        int generateShuDu[][]=new int[10][10];   
        File myFile = new File("D:\\sudoku",inputFilename);
        Reader reader = new InputStreamReader(new FileInputStream(myFile),"UTF-8"); 
        int tempchar;  int i=0; int j=0;
            while ((tempchar = reader.read()) != -1) {  
            if ( (((char) tempchar) != '\n') &&(((char) tempchar) != ' ')) {  
                if(i<m){
                    if(j<m){
                        if(tempchar!=13){
                            generateShuDu[i][j]=((char) tempchar)-48;
                            j++;
                        }
                    }else{  
                        i++;
                        j=0;
                        generateShuDu[i][j]=((char) tempchar)-48;
                    }
                }
                if(i==m){
                    if(n!=0){
                        setShuDu(generateShuDu);
                        shuDu_solution(0,m);
                        n--;
                        i=0;j=0;
                    }
                    
                }
            }  
        }
        reader.close();
    }   

遇到的问题(这个问题耽误了我6个小时左右):

FileWriter fw = new FileWriter("c.txt",true);
fw.write("hello");
fw.close();

文件写入建议用FileWriter

如果用BufferedWriter会导致多次写入时被覆盖!

String outfile="D:\\sudoku\\out.txt";
File writename = new File(outfile); // 相对路径,如果没有则要建立一个新的out.txt文件  
writename.createNewFile(); // 创建新文件  
BufferedWriter out = new BufferedWriter(new FileWriter(writename)); 
out.write(shuDu[i][j]+" ");

异常处理:主要是对文件的读取进行异常处理

1797358-20190922203524049-745809331.png
1797358-20190922203530033-187158221.png
1797358-20190922203611415-1448187242.png
1797358-20190922204026331-376049326.png

单元测试样例:

1797358-20190922201200537-684292598.png

性能测试截图():

1797358-20190922201228690-1655738522.png
1797358-20190922201253642-986903750.png
1797358-20190922201300500-1929698128.png

性能测试我使用的是jprofiler9.2,由于是刚开始接触这个插件,对其一些功能还不太熟悉,研究了一个晚上,还是看不懂数据,不知道类方法耗时在哪里体现,后面再花时间去摸索(仅仅下载这个插件和主程序,然后配置成功到运行,就花了一天时间,我太难了,关键是看不懂这个图上的数据)

最后总结一下:

用java开发感觉if-else流程语句和一些基本语法没有太大的问题,主要是一些常用的类(文件处理类,IO流)使用还不太熟练,导致开发效率低,中途遇到各种各样的bug,以至于气的连晚饭都不想吃了,但自己还是坚持做完了,bug也解决了;除此之外,编辑器的使用也不太熟练,一些类似于命令行传参的细节性的问题也不懂,不过现在懂了,也算是一份收获吧,另外,学会了在github上面上传文件和文件夹,对于github的使用有了初步的认识,希望自己在下次的项目开发中有新的收获,遇到bug不要难过,要勇于挑战bug这样才能不断突破自己!

转载于:https://www.cnblogs.com/lohh/p/11557591.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值