github地址

Sudoku地址

需求

实现一个能生成数独终局并且能够求解数独问题的控制台程序。
能生成不重复的数独终局至文件。
读取文件内的数独问题,求解并将结果输出到文件。
主要需求标黄。

环境

win10+IntelliJ IDEA

PSP

PSP2.1 Person Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 60 60
·Estimate ·预估这个任务需要多少时间 1440 1440
Development 开发 480 600
·Analysis ·需求分析(包括学习新技术) 800 680
·Design Spec ·生成设计文档 60 30
·Design Review 设计复审(和同事审核设计文档) 30 0
·Coding Standard ·代码规范(为目前的开发制定合适的规范) 60 30
·Design ·具体设计 200 140
·Coding ·具体编码 300 420
·Code Review ·代码复审 60 60
·Test ·测试 60 60
·Reporting ·报告 30 30
·Test Report ·测试报告 30 30
·Size Measurement ·计算工作量 60 45
·Postmortem & Process Improvement 事后总结,并计算出过程改进计划 240 120
合计(有重叠部分) 1500

解题思路

问题可以分解成三个部分,一个是数独的生成,一个是解数独,一个是本地化存储和读取。

数独生成

生成部分也可以分步骤来实现:先生成数独终局,然后根据需要挖掉对应的格子数。格子数应当拥有一个上限和一个下限,不同的格子数对应着不同的难度。也可以写死,具体的根据开发进度决定是否加上难度选择的额外需求。
最初的版本可以将一个已经有的数独终局作为基础,并且交换盘上的数字来达到不同棋局的效果。但是这样子做能够生成的棋局有一个上限,不过也能够满足部分的需求。

解数独

最初的设想是可以通过暴力的方式来解数独,当然纯暴力的方式对于时间和空间的消耗都太大了,所以需要借助记忆化存储或者回溯的方式来剪枝。可能在设计不出来的时候会在网上查找参考资料。
已经通过回溯的方法实现了解数独的算法。

本地化存储

需要使用到Java的IO流操作,比较简单,代码如下:

    /*** 从txt文件读取matrix* @param path* @param fileName* @return* @throws IOException*/public static ArrayList<int[][]> readMatrixFromTxt(String path, String fileName) throws IOException {ArrayList<int[][]> result = new ArrayList<>();BufferedReader br = new BufferedReader(new FileReader(path+"/"+fileName));String temp = br.readLine();StringBuilder sb = new StringBuilder();while(temp!=null){sb.append(temp);temp = br.readLine();}String[] matrixs = sb.toString().replaceAll("\n","").split("_");int index = 0;for (String s : matrixs) {index = 0;int[][] matrix = new int[9][9];for(int i = 0 ; i < 9 ; i++){for(int j = 0 ; j < 9 ; j++){matrix[i][j] = s.charAt(index++) - '0';}}result.add(matrix);}return result;}
    /*** 保存数据* @param fileName 文件名称* @param data 对应生成的棋局或解的棋局*/public void saveDataToFile(String fileName,ArrayList<String> data){if(data==null||data.size()==0){System.out.println("data错误");return;}BufferedWriter writer = null;//TODO:添加对应的路径String destDirName=" ";File dir = new File(destDirName);if (dir.exists()) {System.out.println("创建目录" + destDirName + "失败,目标目录已经存在");}if (!destDirName.endsWith(File.separator)) {destDirName = destDirName + File.separator;}//创建目录if (dir.mkdirs()) {System.out.println("创建目录" + destDirName + "成功!");} else {System.out.println("创建目录" + destDirName + "失败!");}File file = new File(destDirName+ fileName + ".txt");System.out.println(file+"file");//如果文件不存在,则新建一个if(!file.exists()){try {file.createNewFile();} catch (IOException e) {e.printStackTrace();}System.out.println(fileName + ".txt文件不存在");}//写入try {writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file,false), "UTF-8"));for(String str:data){writer.write(str+"\n");    }} catch (IOException e) {e.printStackTrace();}finally {try {if(writer != null){writer.close();}} catch (IOException e) {e.printStackTrace();}}System.out.println("文件写入成功!");}

设计实现过程

大部分的类都是用了构造器模式,提升了代码的优雅程度,个人比较喜欢这种RxJava的风格。

FileUtils.writeToTXT(FileUtils.PATH, FormatUtils.formatArray(generator.createSampleArray().generateSudokuArray().createEmptySpace().build()), FileUtils.MATRIX);......
SudokuPuzzleSolver.create().initMatrix(matrix).solve();

生成数独

目前的思路是使用交换法来实现不同的数独终局。期初给定几个种子终局,通过生成随机序列,然后以随机序列的顺序交换每个数字。
每个终局可以随机挖0到12 20个空,所以生成的终局数量是可观的。
以下是核心算法:

    /*** 交换* @return*/public SudokuPuzzleMatrixGenerator generateSudokuArray() {List<Integer> randomList = buildRandomList();for (int i = 0; i < 9; i++) {for (int j = 0; j < 9; j++) {for (int k = 0; k < 9; k++) {if (sampleArray[i][j]==(randomList.get(k))) {sampleArray[i][j] = randomList.get((k + 1) % 9);break;}}}}return this;}/*** 挖空* @return*/public SudokuPuzzleMatrixGenerator createEmptySpace(){Random r = new Random();Integer emptyNum = r.nextInt(12);Integer x;Integer y;for(int i = 0 ; i < emptyNum ; i++){x = r.nextInt(8);y = r.nextInt(8);sampleArray[x][y] = 0;}return this;}

种子矩阵生成自己随意去网上找几个数独终局即可,在java中有个坑就是二维数组深拷贝,需要自己写,不然达不到深拷贝目的,因为java中二维数组相当于数组的数组,clone()方法拷贝的知识一维数组的地址,所以需要自己写。

    /*** 二维数组深拷贝* @param array* @return*/public static int[][] cloneArray(int[][] array){int[][] result = new int[9][9];for(int i = 0 ; i < 9 ; i++){for(int j = 0 ; j < 9 ; j++){result[i][j] = array[i][j];}}return result;}

解数独

回溯法解数独,也就是填了一个空之后递归解决剩下的空,如果无解就返回。

    public SudokuPuzzleSolver solve(){this.backTrace(0,0);return this;}/*** 数独算法** @param i* @param j*/private void backTrace(int i, int j) {if (i == 8 && j == 9) {FileUtils.writeToTXT(FileUtils.PATH,"获得正确解\n",FileUtils.SOLVE);FileUtils.writeToTXT(FileUtils.PATH,FormatUtils.formatArray(matrix),FileUtils.SOLVE);return;}//已经到了列末尾了,还没到行尾,就换行if (j == 9) {i++;j = 0;}//如果i行j列是空格,那么才进入给空格填值的逻辑if (matrix[i][j] == 0) {for (int k = 1; k <= 9; k++) {//判断给i行j列放1-9中的任意一个数是否能满足规则if (check(i, j, k)) {//将该值赋给该空格,然后进入下一个空格matrix[i][j] = k;backTrace(i, j + 1);//初始化该空格matrix[i][j] = 0;}}} else {//如果该位置已经有值了,就进入下一个空格进行计算backTrace(i, j + 1);}}/*** 判断给某行某列赋值是否符合规则** @param row* @param line* @param number* @return*/private boolean check(int row, int line, int number) {//判断该行该列是否有重复数字for (int i = 0; i < 9; i++) {if (matrix[row][i] == number || matrix[i][line] == number) {return false;}}//判断小九宫格是否有重复int tempRow = row / 3;int tempLine = line / 3;for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {if (matrix[tempRow * 3 + i][tempLine * 3 + j] == number) {return false;}}}return true;}

输入异常类

如果输入错误就会抛出这个异常

/*** 参数输入错误的异常*/
public class WrongArgsException extends Exception {}

软件测试

划分为如下等价类:合法输入,非法输入。
合法输入分为:可解矩阵、不可解矩阵、正确生成矩阵、错误数字生成矩阵。
非法输入分为:缺行、多行、非法字符、错误命令行参数、错误命令函参数数量。
规定,0为需要填的空

合法输入

使用如下矩阵:
可解矩阵:

250001879
401879056
879256031
524603798
610798524
798024613
362145987
145987062
987302145

输出结果:

获得正确解
256431879
431879256
879256431
524613798
613798524
798524613
362145987
145987362
987362145
_

无解矩阵:

550001879
501879056
879256031
524503798
610798524
798024613
362145987
145987062
987302145

输出结果:

无解

正确生成矩阵:
-c 20
输出结果:
正确生成文件。
错误数字生成矩阵:
-c -1
输出结果:

请输入正确的参数

非法输入

缺行:

550001879
501879056
879256031
524503798
610798524
798024613
362145987
145987062

输出结果:

数独矩阵错误,请检查矩阵是否合法

多行:

550001879
501879056
879256031
524503798
610798524
798024613
362145987
145987062
987302145
123456789

输出结果:

数独矩阵错误,请检查矩阵是否合法

非法字符:

550001879
501879056
87b256031
524503798
610798a24
798024613
362145987
145987062
987302145

输出结果:

数独矩阵错误,请检查矩阵是否合法

错误命令行参数:
-c abc
输出结果:

Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)at java.base/java.lang.Integer.parseInt(Integer.java:652)at java.base/java.lang.Integer.valueOf(Integer.java:983)at Solution.main(Solution.java:22)

错误命令函参数数量:
-c -1 20
输出结果:

请输入正确的参数

性能分析

使用的是JProfiler,具体安装使用教程:传送门


由于String的底层是byte,所以byte[]的使用量是非常高的,上图是跑20万个矩阵生成时的性能分析。可以通过多线程来跑生成矩阵的代码,这是可以优化的一个点。当然解数独也可以同样这么优化。同时生成数独和解数独时会不断创建Generator和Solver的对象,所以再数量及其大的时候会出现GC。这是可以优化的第二个点。

反思总结

1、git提交的时候应该明确提交格式,github会将第一行抽取为当前commit的标题,所以推荐的格式如下:

【修改类型】 修改描述1、修改描述1
2、修改描述2
...

2、推荐使用阿里的代码规约扫描代码之后按照意见修改之后再上传。
3、应该设置好IDEA新建类时的自动注释生成。

参考资料

回溯法解数独
生成数独的方式

【软件工程基础】数独生成器以及解答器相关推荐

  1. Python基础(八)--迭代,生成器,装饰器与元类

    目录 Python基础(八)--迭代,生成器,装饰器与元类 1 迭代 1.1 可迭代对象与迭代器 1.2 自定义迭代类型 1.3 迭代合体 2 生成器 2.1 什么是生成器 2.2 生成器表达式 2. ...

  2. 软件工程基础-个人项目-数独游戏

    软件工程基础-个人项目-数独游戏 ----------------------------------------------------------------------------------- ...

  3. 软件工程基础个人项目——数独(5)

    软件工程基础个人项目--数独 点击这里可看github上的具体代码 本次个人项目关于数独的生成与求解 PSP表格 PSP2.1 Personal Software Process Stages 预估耗 ...

  4. 软件工程基础——个人项目——数独(1)

    软件工程基础--个人项目--数独(1) 一.实现目标 1.生成数独终局 命令行输入如下: sudoku.exe -c 20 sudoku.exe为最终实现程序,-c确定活动为生成数独终局,20为生成结 ...

  5. 递归函数、生成器、装饰器

    递归函数.生成器.装饰器 递归:  在函数执行中调用自身 必须有边界条件,递归前进段和递归返回段 python中递归有层次限制 递归函数实现阶乘 def fact(n): if n <= 1:r ...

  6. 计算机组成原理的基础知识,计算机组成原理:基础知识部分习题解答(学习笔记)...

    计算机组成原理:基础知识部分习题解答(学习笔记) 1.冯·诺依曼型计算机的主要设计思想是什么?它包括哪些主要组成部分? 答:存储程序并按地址顺序执行,是冯·诺依曼型计算机的主要设计思想.冯·诺依曼型计 ...

  7. leetcode 37 数独问题的解答

    leetcode 37 数独问题的解答 1.问题 请写一个程序来解决数独难题. 数独解决方案应该遵循以下三个规则: 1.每个数字1-9只能在每行中出现一次. 2.每个数字1-9只能在每列中出现一次. ...

  8. IOS开发基础之图片轮播器-12

    IOS开发基础之图片轮播器-12 核心代码 // // ViewController.m // 12-图片轮播器 // // Created by 鲁军 on 2021/2/2. //#import ...

  9. 十一. Python基础(11)—补充: 作用域 装饰器

    十一. Python基础(11)-补充: 作用域 & 装饰器 1 ● Python的作用域补遗 在C/C++等语言中, if语句等控制结构(control structure)会产生新的作用域 ...

  10. python 生成器装饰器_对Python生成器、装饰器、递归的使用详解

    1.Python生成器表达式 1).Python生成器表达式 语法格式: (expr for iter_var in iterable) (expr for iter_var in iterable ...

最新文章

  1. 数据统计分析中的几点
  2. linux中内核中machine_desc,Linux-内核-学习笔记(13):移植三星官方内核
  3. Django(part44)--制作分页
  4. c++从入门到进阶--引用与常量
  5. 牛客网 【每日一题】5月14日题目 maze
  6. 【2018.5.12】模拟赛之二-ssl2414 简写单词【字符串】
  7. fastapi quickstart学习
  8. 插值法补齐缺失数据_数据挖掘非常重要的一步:数据预处理
  9. Android 内存泄漏分析(完)
  10. box2d 计算下一帧的位置/角度
  11. 新手学堂:Linux操作系统的启动步骤说明
  12. [原创]FineUI秘密花园(二十六) — 选项卡控件概述
  13. Java应用基础微专业-设计篇
  14. 机器学习入门好文章--超级推荐
  15. 华为畅享20为什么没有计算机,华为畅享20 Pro强势来袭:即刻5G,不等待
  16. 北大核心期刊目录2021年 计算机类
  17. Distributing Ballot Boxes HDU - 4190【详细翻译】【贪心、二分】
  18. 在电脑浏览器上怎样对一整个页面进行完整的截图?(整站截图)
  19. 微信支付宝个人收款解决方案之免签约支付解决方案之APP监控通知方案
  20. java HashMap练习题1

热门文章

  1. 计算机显示器本身物理分辨率,电脑显示器常见的分辨率是多少
  2. iBase4j项目搭建
  3. 计算机无线网络服务禁用了怎么办,Win10无线网络服务被禁用怎么办 Wlan选项不见无法上网的修复步骤...
  4. WIN10系统——打开PB的帮助文档
  5. 将OpenWRT安装到 X86 电脑硬盘中
  6. 一个简单的微信小程序支付demo
  7. Awesomium(二)-- MultiThreadSnapshot
  8. socat使用指南:3:5种常见的使用方法
  9. Unity2D:简单人物纸娃娃换装实现(一) 服装的变换
  10. 跟着莫烦python 从零开始强化学习之Q-Learning 一维探索者 代码完整注释版