参见GitHub:https://github.com/1773262526/Software-Foundation

Personal Software Process Stages         预估耗时(分钟)       实际耗时(分钟)      
计划 30  30 
估计这个任务需要多少时间    
开发  120  60
需求分析(包括学习新技术)  150  180
生成设计文档  120  120
设计复审(和同事审核设计文档)  60  60
代码规范  60  30
具体设计  90 90 
具体编码  1440  1200
代码复审  120  180
测试  300  600
报告  120  180
测试报告  90  120
计算工作量  30  30
事后总结,并提出过程改进计划  45 30 
合计  2775 2910 

解题思路:

主要任务分为两部分,一个是生成数独,另一个是求解数独。

在生成数独方面,主要有两种方法

  1. 通过求解数独,利用回溯等方法,不断对一个初始残缺数独局进行求解,得到满足数量的终局。
  2. 通过对一个已知合法数独终局进行一定的变换,变换结果仍然满足数独规则,获得更多的数独终局

在第一种方法中,能够保证结果的互异性,但需要大量的计算,不断填充验证结果是否合法,极其浪费时间,所以我选择通过对已知数组的变换获得结果。

设计实现过程:

  数独的生成和求解是一个相当广泛的既成题目,已经有很多前人总结了足够的方法,代码中主要的部分可以通过对网上既成的代码进行适当改进,所以没有必要闭门造轮。

  经过查阅相关资料,比较简单容易实现的变换原则有:对两个数进行互换、两行/两列之间互换(只能在共同区域内互换,如第一列第四列不能互换)、整个三行/三列为一个块进行互换、九宫格的2.4.6.8宫进行轮转,这些操作都不会破坏原数独方阵的合法性。

3.27note:可以选择手动制作50个(时间充足的情况下可以使用更多)原始数独方阵,每次生成时随机选择其中一个作为模板,然后利用随机数随机多次进行上述操作,基本可以保证不出现重复方阵。

  生成数独

  经过实践,3.27的想法能够成功实现大量不重复的方阵,但是由于随机数的不可控性、仍然不能100%保证生成方阵数量极大时的互异性,若采用生成之后进行验证的方法,既浪费空间,优惠耗费大量时间,所以尽量保证数独方阵的生成中必然不会出现重复个体。(下文随机生成数组的代码已经改换成全排列进行)。   

  经过查阅,数独方阵的的每行可以由第一行经过推移特定位数得到,这种方法的到的方阵同样满足数独的规则。比如第2-9行分别将第一行右移3、6、1、4、7、2、5、8行。

  使用这种方法,能够从数独生成阶段杜绝重复数独发生的可能性。由于固定了第一行首位数的选择,所以这种方法可以生成8!=40320种终局,在与前沿的转换规则相结合,则能够轻易满足1e6种数独终局的要求。例如,要求1e6种终局,则由1e6/40320=24,需要至少25种变换方法,所以我们需要令每25个方阵使用相同的第一行。由于我的数组方阵使用了字符串进行处理,所以进行行变换更加方便,我采用“第4-6行中任意两行互换” * “第7-9行中任意两行进行互换” * “第2/3行进行互换”,这样有32种变换方法,足够满足需求。

  对生成的数组进行计数,每32个动用一个首行的排列方式。在相同的首行生成的32个数组中,奇数组前三行不变,偶数组的第二三行进行互换。在此基础上,通过4-6,7-9行的变换,能够组合出4 * 4种方式。

    代码说明

//行互换操作
void exch_row(char sudu[][], int op)
{
if (op % )
switchsudo(sudu, , );
op /= ;
switch (op)
{
case :return;
case :switchsudo(sudu, , ); break;
case :switchsudo(sudu, , ); break;
case :switchsudo(sudu, , ); break;
case :switchsudo(sudu, , ); break;
case :switchsudo(sudu, , ); break;
case :switchsudo(sudu, , ); break;
case :switchsudo(sudu, , ); switchsudo(sudu, , ); break;
case :switchsudo(sudu, , ); switchsudo(sudu, , ); break;
case :switchsudo(sudu, , ); switchsudo(sudu, , ); break;
case :switchsudo(sudu, , ); switchsudo(sudu, , ); break;
case :switchsudo(sudu, , ); switchsudo(sudu, , ); break;
case :switchsudo(sudu, , ); switchsudo(sudu, , ); break;
case :switchsudo(sudu, , ); switchsudo(sudu, , ); break;
case :switchsudo(sudu, , ); switchsudo(sudu, , ); break;
case :switchsudo(sudu, , ); switchsudo(sudu, , ); break;
}
}

    求解数独

  最初为了能够尽快的找出数独的答案,采用了摒弃求解和搜索并行的方式,但是在实践中容易产生bug,且在实践中发现比经典回溯方法节约的时间完全无法察觉,另外在求解数独部分因为没有性能要求,所以可以认为用户没有在短时间内求解大量数独的需求,而对于少数数独的求解,不同算法之间的时间差几乎无法察觉,所以没必要为了节约极少的时间浪费较多精力。这里采用了经典的回溯法,每次遇到空的位置时查询1-9是否合法,若合法则进行下一层探索,直到行不通回退回来,继续回溯,目前没有遇到无法求解的数独,且所有测试数独都能瞬间给出答案。此处不再赘述。

void backtrace(char sudu[][], int cont)
{
if (cont == || flag)
{
flag = ;
return;
}
int row = cont / ;
int col = cont % ;
if (sudu[row][col] == '')
{
for (int i = ; i <= ; ++i)
{
sudu[row][col] = i + '';//赋值
if (isPlace(sudu, cont)) //可以放
{
backtrace(sudu, cont + );//进入下一层
if (flag)
return;
}
}
sudu[row][col] = '';//回溯
}
else
backtrace(sudu, cont + );
}

    函数概述

  除主函数外有10个函数,调用关系如下:

  程序流程图通过vs自动生成,见体系结构。只是画出了各个函数的调用联系,并没有注明跳转的判断条件。好像不如ida生成的舒服。。

性能分析、改进:使用生成1e6个数独终局进行性能测试。

  首先是CPU占用率

 然后是CPU采样、检测、内存分配和占用的检测

根据以上两项,不难发现瓶颈主要在于写入文件和生成数独中对第一行的后移。

  首行的后移是解决方案的硬性要求,而且已经压缩到线性,无法再继续优化。

  写入文件部分:输出是采用按字符输出,即从9*10的字符数组中一位一位的输出,在每个字符之间穿插空格换行。按照函数调用约定,会将参数传进寄存器,再将函数调用地址和返回地址分别压栈,在出栈时进行函数调用。这样每个数独方阵输出时需要多进行近两百次栈操作,数组数量极大时会在函数调用方面浪费很多时间,所以直接改成在程序中将方阵字符和空格等直接编入一个字符串,只执行一次写文件函数,将整个字符串写入,理论上能够节省一定的时间。

//改进后的写入操作
//后移操作
void pushback(char su[], int m, char sou[])
{
char cop[] = { };
strcpy(cop, sou);
int i;
for (i = ; i < ; i++)
su[(i + m) % ] = cop[i];
}

    经过改进,能够在1-2秒内生成1e6组数组并写入文件(比之前并没有可见的提高),足以满足性能需求,

    求解数独部分因为没有性能要求,便不再展示其性能分析部分。

单元测试部分

  经过断断续续三四天的纠缠,总算弄好了单元测试部分。可以使用vs提供的单元测试项目进行单元测试,使用Assert断言某项。          ///困死了,,再不睡要去见图灵了

  单元测试部分还是比较麻烦,因为在实现代码的时候,已经经过重复的输出测试和相关debug环节,基本排除掉了bug,能够在各种情况下实现预期的输出,而且进行测试的用例是学生自己提供,能够想到的坑早已经和同学交流过,并且在编码中解决。所以这项工作其实非常鸡肋,唯一的作用就是学习一下单元测试的使用,算是提前学习了这种操作。

  之前没有用过单元测试,还是靠着百度和同学的帮助下,才成功找到了新建单元测试项目的按钮,但还是遇到能够成功编译单元测试代码,但是在测试资源管理器不能显示测试结果。之后发现是项目的配置在之前胡改成了debug(活动)还有链接库也根据其他的博客改的面目全非,改回debug、动态链接库就能够正常显示。

     

  在测试模块中,通过自行定义变量,可以调用数独项目中的函数,根据断言返回值或者变量的改变,判断函数是否能够在不同情况下,实现预期的功能。

  代码覆盖率分析

    代码覆盖率在“测试/窗口/代码覆盖率结果”或选择“分析代码覆盖率”。

  

    在最初的代码覆盖率分析的时候,发生了一些意外,由于未知原因导致明明运行的测试,却提示未检测二进制文件。

      经过几番尝试在本项目中始终无法正确得出结果,转移到同学的vs2017上经过配置项目属性,能够得到,判断应该是在之前尝试乱改属性进行单元测试的时候改崩了。

      另新建了项目,确定活动解决方案平台为x86,两个项目文件的配置都是debug,平台选择win32便能够顺利得出覆盖率分析。

      

      

      最后晒一下几经修改把代码覆盖率从80.74提高到98.06。

      

      最终整体覆盖率和各函数的测试覆盖率。

      

我想说

  随着博客的完工,GitHub的同步也逐步完成,旷日持久的单人项目终于宣告结束。这段时间,除了喜闻乐见的bug,更让人闹心的应该算是单元测试和覆盖率这些魔性的新东西了。之前完全没有用过这玩意,老师也完全没有指导,网上又没有可靠的教程,甚至是错误的引导(雾),单元测试部分按照错误的引导乱改配置,直接导致了覆盖率测试的严重问题。

  不过也算是一次历练吧,从之前的码题,到真正自己去按照流程去做这么一个东西,确实多了很多意料之外的困难,为了能够解决这些问题,也经历了连续一周熬到一点的舒爽,同时也学到了很多东西,不过也真的耽误了很多其他的学习。

<软件工程基础>个人项目——数独的更多相关文章

  1. [BUAA2017软工]第1次个人项目 数独

    [BUAA软工]第1次作业 个人项目 数独 一.项目地址 github地址:https://github.com/BuaaAlen/sudoku 二.PSP表格 三.解题思路描述 在拿到这个题目时,我 ...

  2. [buaa-SE-2017]结对项目-数独程序扩展

    结对项目-数独程序扩展 step1~step3:github:SE-Sudoku-Pair-master step4:github:SE-Sudoku-Pair-dev-combine step5:g ...

  3. 二级py--day5 软件工程基础

    二级py--day5软件工程基础 软件工程基础 1.软件工程三要素:方法.工具和过程 2.软件生命周期可以分为:项目可行性研究与规划.软件需求分析.软件设计.软件实现.软件测试.软件运行与维护等阶段 ...

  4. 老王Python培训视频教程(价值500元)【基础进阶项目篇 – 完整版】

    老王Python培训视频教程(价值500元)[基础进阶项目篇 – 完整版] 教学大纲python基础篇1-25课时1.虚拟机安装ubuntu开发环境,第一个程序:hello python! (配置开发 ...

  5. Spring Boot 构建电商基础秒杀项目 (十二) 总结 (完结)

    SpringBoot构建电商基础秒杀项目 学习笔记 系统架构 存在问题 如何发现容量问题 如何使得系统水平扩展 查询效率低下 活动开始前页面被疯狂刷新 库存行锁问题 下单操作步骤多,缓慢 浪涌流量如何 ...

  6. Spring Boot 构建电商基础秒杀项目 (十一) 秒杀

    SpringBoot构建电商基础秒杀项目 学习笔记 新建表 create table if not exists promo ( id int not null auto_increment, pro ...

  7. Spring Boot 构建电商基础秒杀项目 (十) 交易下单

    SpringBoot构建电商基础秒杀项目 学习笔记 新建表 create table if not exists order_info ( id varchar(32) not null defaul ...

  8. Spring Boot 构建电商基础秒杀项目 (九) 商品列表 & 详情

    SpringBoot构建电商基础秒杀项目 学习笔记 ItemDOMapper.xml 添加 <select id="listItem" resultMap="Bas ...

  9. Spring Boot 构建电商基础秒杀项目 (八) 商品创建

    SpringBoot构建电商基础秒杀项目 学习笔记 新建数据表 create table if not exists item ( id int not null auto_increment, ti ...

随机推荐

  1. SDRAM的引脚封装标准

    SDRAM从发展到现在已经经历了五代,分别是:第一代SDR SDRAM,第二代DDR SDRAM,第三代DDR2 SDRAM,第四代DDR3 SDRAM,第五代DDR4 SDRAM.第一代SDRAM采 ...

  2. 新年上新!极光认证 Web SDK 首版上线

    新年伊始,极光开发者服务也抢先为各位开发者朋友带来了"新年大礼包",几款明星产品都悉数有不少更新: 极光认证 Web SDK 版本上线 相信不少小伙伴早已熟知极光认证这款产品,3秒 ...

  3. springboot 报错 org.springframework.beans.factory.NoSuchBeanDefinitionException:No qualifying bean of type 'com.example.service.HrService' available: 有没有大佬出个主意,我找了一天,刚入门springboot

    话不多说先上图,这是启动类的配置,这里配置了@ComponentScan("我的mapper的接口") 接下来是我的项目结构截图 然后是service 的截图,我在这里加了注解@S ...

  4. 2019sdqdCSP-J游记

    特别鸣谢:Miku -------------------------- 中午上了车,和ljx坐在一块.太阳是多么好啊,我们在看着刚出的tg题,cmz找不到了准考证,sbl在临时打印准考证 等到好不容 ...

  5. windows下安装openjdk

    redhat版openjdk,解压后就能用,下载地址https://developers.redhat.com/products/openjdk/download. Azul Zulu版openjdk ...

  6. final关键字在JVM中的理解

    我们先来看两段代码与运行结果,根据结果去分析解释一下 不加final关键字: package com.waibizi; public class demo02 { public static void ...

  7. idea 阿波罗(apollo)设置

    项目启动时需要配置环境

  8. Python循环引用的解决方案

    1.延迟导入:即将 from xxx import yyy 放到函数或类的内部,从而使其作用域变成局部的,但是这样可能会对性能有些影响: 2.将 from xxx import yyy 转换成 imp ...

  9. 婴儿潮一代\千禧一代\X一代\Z一代含义

    (一)婴儿潮一代 婴儿潮一代是指各国的生育高峰期.每个时期的婴儿潮一代都会给各国经济带来不小的刺激作用,但同样的当他们退休时也会给政府的养老保险带来巨大的压力. 在中国,从统计数据来看生育高峰出现在1 ...

  10. html无卡顿动画实现——requestAnimationFrame

    <!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> ...