https://github.com/Slontia/Sudoku2

2. PSP

软件工程基础-结对项目作业

3. 看教科书和其它资料中关于Information Hiding, Interface Design, Loose Coupling的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的。

 

  • GUI 与 Core 的接口

  一切的解数独、生成数独的函数都只需要调用 Core 类中的方法,而不必关心 Core 中的具体实现方法和数据存放,有 Information Hiding 的思想

  • create_puzzle 与 dig 的接口

  为了快速生成多空单解数独的题目,我们分成了两部,第一步是进行推导式挖空,调用 dig 函数;如果第一步不成功,则第二步进行随机挖掘。

  其中 dig 需要比较复杂的数据结构,和随机挖空的情形十分不同,因此设计接口和随机挖掘耦合:

int dig(int mat[SIZE*SIZE], int out[SIZE*SIZE], int dig_count);

dig 的界面很简单:输入数独矩阵 mat 和挖掘目标 dig_count,输出挖空结果到 out,返回实际挖空个数。dig 内部在函数栈里需要生成特殊的数据结构,但对外部都不可见。

同时,这里运用了 Louse Couping 的设计,out 可以视为传递的介质,这里牺牲了效率,将矩阵读入进行处理再读出,而不是和随机挖空使用统一的数据结构,但是这个代价相比尝试挖空的过程,消耗可以不计,而增加了开发的敏捷性。

  • UnitMaps 和 FgMap 的接口

FgMap 是一个类,用来记录每个单元(宫、列、行)的逻辑属性;UnitMaps 则统合了一共 9 * 3 个 FgMap,UnitMaps 通过调用 FgMap 的方法,对 FgMap 进行遍历访问,将信息填入 FgMap 并且获得 FgMap 的逻辑结论,而不用关心 FgMap 中的信息如何管理和结论如何得出。

  • dig 和 UnitMaps 的接口

  dig 将数字信息输入 UnitMaps,从 UnitMaps 中获得当前被其他数字确定的位置,进行挖空,从而使得被挖掉的数字可以被其他数字(用数独的一个定理的子集)推导出来。  

4. 计算模块接口的设计与实现过程。设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(不必列出源代码),以及独到之处。

  主要有三个模块:

  1. create 模块

  这次作业虽然指定不允许出现等价数独,但我们还是可以通过模板变换快速生成1000000个数独。首先,我们需要保证数独的等价性,我们的做法是不对第一个宫进行任何操作(随机性貌似在-c不做要求?)。我们沿用了刘畅同学在个人项目中使用的3-3-3位置轮换方法成功生成了一个数独终盘。在个人项目中,模板的变化主要体现在R4~R6、R7~R9的行变换,但其实,对于3-3-3位置轮换方法生成的终盘,部分行的变换也是可行的,如下图:

 软件工程基础-结对项目作业

 

   由此可以看出,改变部分行可以产生12种排列,而不同颜色之间的变化由相互独立,R4~R6和R7~R9相互独立,共有6组相互独立的变换,共可以产生12的6次方共2985984种排列。

  2. solve 模块

  solve模块的实现和个人项目相同。

  3. puzzle 模块

  软件工程基础-结对项目作业

  独到之处:

  1. 选择最有效的随机方式。我们在随机挖空的时候,发现纯粹随机的效果并不好,特别是要求生成55个空的独解数独时,会比较慢。但是如果在各个宫内部进行比较平均的挖空,得出来的空会分布得比较均匀,就容易产生单解数独。

  2. 结合逻辑推导。反向使用行列宫摈除法,可以挖掉当前局面来看具有逻辑必然性的数字(比如说第一行是 1 2 3 4 5 6 7 8 9,那么 1 可以被挖掉,因为 1 的存在是被其他 8 个数决定的),这样,这部分的挖空可以不用交给随机挖空来解决,降低了算法的时间复杂度。

5. UML

软件工程基础-结对项目作业6)计算模块接口部分的性能改进。记录在改进计算模块性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由VS 2015/2017的性能分析工具自动生成),并展示你程序中消耗最大的函数。

1. puzzle 模块

  1. 测试指令 -n 10000 -r 20~35 -u

软件工程基础-结对项目作业

2. 测试指令 -n 10000 -r 36~50 -u

软件工程基础-结对项目作业

    3. 测试指令 -n 10000 -r 51~55 -u  

     软件工程基础-结对项目作业

    `可见,FgMap::outside_lock 是最耗时间的函数,编写之前已经考虑到了这个问题,因此采用了位运算等加速方法,实际上很难再优化了。

2. create 模块

  软件工程基础-结对项目作业

    create 采用的是行列交换的方法生成不等价的数独,因此很快,瓶颈在 io,但这里采用了缓存一次性输出的方式,最大限度利用了 block 读写的功能,因此也无法再优化了。

3. solve 模块

 软件工程基础-结对项目作业

7)描述这些做法的优缺点, 说明你是如何把它们融入结对作业中的。

契约式编程:

前置条件(pre-condition)、后置条件(post-condition)和不变式(invariant), 分别指代函数核心逻辑 运行前必须满足的条件、运行后必须满足的条件和运行结果必须保持的不变式(某一不变条件)。

整个sudoku可以认为遵循这样的契约:

1. 前置条件:命令行参数符合语法,数值范围在作业要求范围内(否则 read_command 在读入命令时报错)

2. 后置条件:输出符合要求的数独的解或数独题

3. 不变式:输出符合要求的数独的解或数独题

至于内部各个模块的交互,也在一定程度上应用了契约式编程的思想用于调试,例如:

        if ((~map[F2INDEX(figure_x)]) & INDEX2TARGETBIT(index)) {
            //  假如原来这个可能性已经被删除了,意味着取反以后这个位置为 1
            limit[F2INDEX(figure_x)][index] --;
            assert(limit[F2INDEX(figure_x)][index] >= 0);
            if (limit[F2INDEX(figure_x)][index] == 0) {
                pos_count[F2INDEX(figure_x)]++;
                map[F2INDEX(figure_x)] |= INDEX2TARGETBIT(index);
            }
            ret = true;
        }

  这里对 limit 进行判断,要是它小于 0,就直接 crash。这要求其他部分对 limit 处理是恰当的,否则这块代码将无法正确执行。

  

bool UnitMaps::fill_in(int figure, int i, int j) {
    int group_id = GET_GROUP_ID(i, j);
    blank--;
    assert(figure != 0);
    assert(matrix[i][j] == 0);
        // ......
}

  这里要求一定在数独矩阵填 0 的地方才能填数,而且填的数不能是 0 。假如其他部分指定figure , i , j 不正确,就可以及时报错。

  优点:

  如果把模块尽量做得小而可以维护,那么契约式编程可以极大提高程序的正确性,而且可以极大降低调试的代价。

  缺点:

  1. 全面考虑三个条件比较复杂,特别是系统还没有实现的时候,事实上输入输出比较复杂的时候,也很难全面考虑清楚。

  2. 无论输入是否可以信任,程序运行时总在判断合法性,效率比较低。

  3. 软件总是容易有 bug ,契约式编程实际上很难维持契约的严格正确性。

8)计算模块部分单元测试展示。展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试得到的测试覆盖率截图,发表在博客中。要求总体覆盖率到90%以上,否则单元测试部分视作无效。

我们沿用了个人项目中的字典树重复性判断,这次新添了检测等价性的功能。我们选取第一个宫,对里面的数字分别和1~9进行映射,之后将整个数独根据映射刷新,再放入字典树中进行判断。映射的代码如下:

1 char digit_map[SIZE];
2                 for (int i = 0; i < SIZE; i++) {
3                     digit_map[i] = (*sudoku)[i]; // build map
4                 }
5                 /* change to equivalence */
6                 for (int i = 0; i < SIZE * SIZE; i++) {
7                     (*sudoku)[i] = digit_map[(*sudoku)[i] - '1']; 
8                 }
View Code

相关文章:

  • 2021-11-13
  • 2021-05-18
  • 2021-05-16
  • 2021-11-24
  • 2021-06-09
  • 2021-09-11
猜你喜欢
  • 2021-08-22
  • 2021-11-19
  • 2021-08-30
  • 2021-05-28
  • 2022-01-14
相关资源
相似解决方案