【软件工程基础】数独生成器以及解答器
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新建类时的自动注释生成。
参考资料
回溯法解数独
生成数独的方式
【软件工程基础】数独生成器以及解答器相关推荐
- Python基础(八)--迭代,生成器,装饰器与元类
目录 Python基础(八)--迭代,生成器,装饰器与元类 1 迭代 1.1 可迭代对象与迭代器 1.2 自定义迭代类型 1.3 迭代合体 2 生成器 2.1 什么是生成器 2.2 生成器表达式 2. ...
- 软件工程基础-个人项目-数独游戏
软件工程基础-个人项目-数独游戏 ----------------------------------------------------------------------------------- ...
- 软件工程基础个人项目——数独(5)
软件工程基础个人项目--数独 点击这里可看github上的具体代码 本次个人项目关于数独的生成与求解 PSP表格 PSP2.1 Personal Software Process Stages 预估耗 ...
- 软件工程基础——个人项目——数独(1)
软件工程基础--个人项目--数独(1) 一.实现目标 1.生成数独终局 命令行输入如下: sudoku.exe -c 20 sudoku.exe为最终实现程序,-c确定活动为生成数独终局,20为生成结 ...
- 递归函数、生成器、装饰器
递归函数.生成器.装饰器 递归: 在函数执行中调用自身 必须有边界条件,递归前进段和递归返回段 python中递归有层次限制 递归函数实现阶乘 def fact(n): if n <= 1:r ...
- 计算机组成原理的基础知识,计算机组成原理:基础知识部分习题解答(学习笔记)...
计算机组成原理:基础知识部分习题解答(学习笔记) 1.冯·诺依曼型计算机的主要设计思想是什么?它包括哪些主要组成部分? 答:存储程序并按地址顺序执行,是冯·诺依曼型计算机的主要设计思想.冯·诺依曼型计 ...
- leetcode 37 数独问题的解答
leetcode 37 数独问题的解答 1.问题 请写一个程序来解决数独难题. 数独解决方案应该遵循以下三个规则: 1.每个数字1-9只能在每行中出现一次. 2.每个数字1-9只能在每列中出现一次. ...
- IOS开发基础之图片轮播器-12
IOS开发基础之图片轮播器-12 核心代码 // // ViewController.m // 12-图片轮播器 // // Created by 鲁军 on 2021/2/2. //#import ...
- 十一. Python基础(11)—补充: 作用域 装饰器
十一. Python基础(11)-补充: 作用域 & 装饰器 1 ● Python的作用域补遗 在C/C++等语言中, if语句等控制结构(control structure)会产生新的作用域 ...
- python 生成器装饰器_对Python生成器、装饰器、递归的使用详解
1.Python生成器表达式 1).Python生成器表达式 语法格式: (expr for iter_var in iterable) (expr for iter_var in iterable ...
最新文章
- 数据统计分析中的几点
- linux中内核中machine_desc,Linux-内核-学习笔记(13):移植三星官方内核
- Django(part44)--制作分页
- c++从入门到进阶--引用与常量
- 牛客网 【每日一题】5月14日题目 maze
- 【2018.5.12】模拟赛之二-ssl2414 简写单词【字符串】
- fastapi quickstart学习
- 插值法补齐缺失数据_数据挖掘非常重要的一步:数据预处理
- Android 内存泄漏分析(完)
- box2d 计算下一帧的位置/角度
- 新手学堂:Linux操作系统的启动步骤说明
- [原创]FineUI秘密花园(二十六) — 选项卡控件概述
- Java应用基础微专业-设计篇
- 机器学习入门好文章--超级推荐
- 华为畅享20为什么没有计算机,华为畅享20 Pro强势来袭:即刻5G,不等待
- 北大核心期刊目录2021年 计算机类
- Distributing Ballot Boxes HDU - 4190【详细翻译】【贪心、二分】
- 在电脑浏览器上怎样对一整个页面进行完整的截图?(整站截图)
- 微信支付宝个人收款解决方案之免签约支付解决方案之APP监控通知方案
- java HashMap练习题1
热门文章
- 计算机显示器本身物理分辨率,电脑显示器常见的分辨率是多少
- iBase4j项目搭建
- 计算机无线网络服务禁用了怎么办,Win10无线网络服务被禁用怎么办 Wlan选项不见无法上网的修复步骤...
- WIN10系统——打开PB的帮助文档
- 将OpenWRT安装到 X86 电脑硬盘中
- 一个简单的微信小程序支付demo
- Awesomium(二)-- MultiThreadSnapshot
- socat使用指南:3:5种常见的使用方法
- Unity2D:简单人物纸娃娃换装实现(一) 服装的变换
- 跟着莫烦python 从零开始强化学习之Q-Learning 一维探索者 代码完整注释版