高级软件工程第二次作业

个人项目实践

我的Github项目地址

项目地址https://github.com/pfq1990/ShukuProject.git
编译环境: Windows 7 x64
开发环境: Visual Studio Community 2017
开发语言: C++

PSP表格项目开始前各个模块上预计花费的时间

PSP2.1 Personal Software Process Stages 预估耗时(分钟)
Planning 计划
· Estimate · 估计这个任务需要多少时间 30
Development 开发
· Analysis · 需求分析 (包括学习新技术) 30
· Design Spec · 生成设计文档 30
· Design Review · 设计复审 (和同事审核设计文档) 30
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 30
· Design · 具体设计 30
· Coding · 具体编码 100
· Code Review · 代码复审 100
· Test · 测试(自我测试,修改代码,提交修改) 100
Reporting 报告
· Test Report · 测试报告 60
· Size Measurement · 计算工作量 40
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 100
合计 680
注:以上为大致预估时间

解题思路描述

项目相关要求描述

  • 利用程序随机构造出N个已解答的数独棋盘
    • 输入:数独棋盘题目个数N;
    • 输出:随机生成N个不重复的已解答完毕的数独棋盘,并输出到sudoku.txt中。

项目解决思路描述

  • 在看到项目之后我的初步想法就是,如下图所示:

    这样关键点就集中于如何生成数独棋盘;
  • 此时我无意中浏览到了“_Lei”的一篇博文-"数独 C++版",这样我就有了想法:构造一个数独类,利用这个类生成数独棋盘。
  • 同时我又发现上面这篇博文有一个问题就是生成的数独棋盘是永恒不便的,我的解决思路就是随机生成数独棋盘的第一行元素,并在此基础上构造数独棋盘。
    以上就是我解决这个项目过程中的全部思路。

注:数独 C++版博文地址

设计实现过程

参考上面那篇博文,我设计了一个数据结构Node,两个类:一个是数独元素类取名Element,另一个是数独类取名Shuku;然后用main函数调用这些类实现需求所提出的功能。

  • Node结构体包含两个整型元素,用于标识元素所在的行列位置
    • x:该元素所在的行位置
    • y:该元素所在的列位置
  • Element类,设计了四个成员变量,并围绕着四个变量设计数个成员函数
    • 成员变量
      • node :结构体Node类型,用于标识元素所在的行列位置;
      • isProcessed :bool类型,用于在数独棋盘创建过程中标识该元素是否是满足数独条件;
      • valueList :整型列表类型,用于存储满足数独条件的有效数字,起到一个队列的作用;
      • value :整型用于标识该元素的值,允许公共访问。
    • 成员函数
      • X()、setX(int x):操作node成员的x的值 ,前者为获取该成员的值,后者为设置该成员的值;
      • Y()、setY(int y):操作node成员的y的值,前者为获取该成员的值,后者为设置该成员的值;
      • IsProcessed()、setIsProcessed(bool ip):操作isProcessed的值,前者为获取该成员的值,后者为设置该成员的值;
      • IsValueListEmpty():返回值为bool类型,检查valueList列表是否为空;
      • AddNextValue(int n):功能为将数值n添加到valueList列表中;
      • PickNextValue():功能为从valueList列表中将第一个有效值取出,并赋值给value,将isProcessed标志设为true,此后将不对这个元素做操作。
  • Shuku类,设计了一个成员shuku为Element类型的9×9二维数组,并围绕这个成员设计了10个函数
    • valueInRow(int v, int x, int y):检查所在行所有处理过的元素,验证值v是否合适放在x行y列;

    • valueInCol(int v, int x, int y):检查所在列所有处理过的元素,验证值v是否合适放在x行y列;

    • valueInSmallShuku(int v, int x, int y):检查所在九宫格中元素,验证值v是否合适放在x行y列;

    • NextNode(Node& n):获取当前数独结点的下一个位置的结点;

    • PrevNode(Node& n):获取当前数独结点的前一个位置的结点;

    • GetNextValue(Element* e):逐个检查1-9元素,是否为e元素所在的位置满足数独定义的有效数值,如果满足则加入e的有效数值队列中;

    • InitShuku():该函数实现两个功能,一是初始化9×9二维数组shuku的每一个元素;二是生成一个1-9的随机顺序的数组赋予shuku的第一行;

    • GenerateShuku():在初始化shuku二维数组后,将未处理位置设置成满足数独定义的值,也就是生成数独棋盘,该函数为本项目的核心函数,流程图如下所示:

    • PrintShuku():将shuku表示的数独棋盘打印到屏幕上;

    • WriteFile(ofstream& out):将shuku表示的数独棋盘写入到out文件流中,即将生成的数独棋盘输出到文件中。

  • main函数实现过程,根据该项目的设计思路,我利用上面设计类实现,具体流程图如下所示:

关键代码说明

  • 本项目的关键代码有两段:一段是main函数的实现代码;另一段是生成数独的代码,即Shuku类中的GenerateShuku()函数
    • main函数的实现代码

         int main(int argc,char** argv)
        {
        int n;//需要生成的数独棋盘数量
        ofstream out("sudoku.txt");//定义个文件流指向需要输出的文件路径
        //判断输入是否合法
        if (argc == 3) //判断输入的参数是否满足规定长度
        {
                if (strcmp(argv[1],"-c")==0) //检测输入是否满足约定格式,-c 生成的数独棋盘的数量
                {
                    if (!isnum(argv[2])) //检测输入参数是否为数字
                    {//如果输入不是整数则提示用户输入一个整数
        	            cout << "Error input parameter" << endl<<"Please re-enter an integer:";
        	            cin >> n;
                    }
                    else 
                    {//如果满足条件则读入参数
                            n = atoi(argv[2]);
                    }
                }
                else
                {//如果输入格式不满足约定格式,则提示错入,并让用户重新输入一个数字
                    cout << "Error input parameter" << endl << "Please re-enter an integer:";
                    cin >> n;
                }
        }
        else
        {//如果输入格式不满足约定长度,则提示错入,并让用户重新输入一个数字
            cout << "Error input parameter" << endl << "Please re-enter an integer:";
            cin >> n;
        }
        //判断输入的数字是否为0
        while (n == 0) 
        {//如果输入为0,则提示错入,并让用户重新输入一个数字
            cout << "Error input parameter" << endl << "Please re-enter an integer:";
            cin >> n;
        }
        Shuku *sk=new Shuku[n];//动态生成n个数独
        srand(unsigned(time(NULL)));//产生随机数种子,防止每次产生的随机数相同
        for (int i = 0; i < n; i++) 
        {
            sk[i].GenerateShuku();//调用创建数独棋盘的函数
            sk[i].PrintShuku();//将数独棋盘打印到屏幕
            sk[i].WriteFile(out);//将数独棋盘写入到文件中
            cout << endl;
            out << endl;
        }
        delete[] sk;//释放占用空间
            return 0;
        }
      
    • Shuku类中的GenerateShuku()函数

        /*
        ** 创建数独
        */
        void Shuku::GenerateShuku()
        {
            Node currentNode;//设置临时存储节点
                //初始化节点x和y为0指向shuku第一个位置
            currentNode.x = 0;
            currentNode.y = 0;
      
            Element* currentElement = NULL;//设置临时存储元素
            while (true) {
                currentElement = &shuku[currentNode.x][currentNode.y];//获取shuku当前位置的元素
                if (!currentElement->IsProcessed())//判断该元素是否已经处理
                        {
                    GetNextValue(currentElement);//如果未处理则查找该位置的有效值
                }
                if (!currentElement->IsValueListEmpty()) //判断有效值队列是否为空
                        {
                    currentElement->PickNextValue();//如果不为空则对该位置元素进行设置,设置该位置值为有效值队列对头值,并标记为已处理
                        if (currentNode.x == MaxSize - 1 && currentNode.y == MaxSize - 1) //判断该元素是否是shuku最后一个元素
                                {
        	            break;//如果是则说明操作结束,数独棋盘生成完成,操作返回
                    }
                    else {
        	            NextNode(currentNode);//如果不是则处理下一个节点
                    }
                }
                else {//如果队列为空
                    if (currentNode.x == 0 && currentNode.y == 0) //判断元素位置是否为第一个位置
                                {
        	            break;//如果是则说明数独棋盘已经生成,操作返回
                    }
                    else {//如果不是,则说明当前情况下数独无解
        	            currentElement->Clear();//清空对当前元素的操作
        	            PrevNode(currentNode);//返回上一个节点操作
                    }
                }
            }
        }
      

测试运行

  • 正常情况下测试的结果,输入sudoku.exe -c 3,运行结果如下所示:

  • 错误情况下的测试结果

    • 输入 sudoku.exe -c abc,参数不满足数字情况下的运行结果如下所示:

    • 输入sudoku.exe -c 0,参数为0情况下的运行结果如下所示:

    • 输入错误,按照提示手动输入后输出的结果如下图所示:

性能分析与该改进

  • 通过Visual Studio Community 2017中的Visual Studio Profiling Tools工具分析原来程序性能,如下图所示:

    通过上图的分析主要耗费时间Shuku::GenerateShuku、Shuku::PrintShuku和Shuku::WriteFile三个函数,而Shuku::GenerateShuku为本项目的核心函数,核心算法是我目前能想到的最简算法,但是Shuku::PrintShuku和Shuku::WriteFile两个函数结构和功能上类似,如果将这两个函数合成在一起能够提高程序的性能。

  • 改进后Visual Studio Profiling Tools工具分析改进后程序性能,如下图所示:

    通过这样改进性能有所提高,但是要想提高整体性能必须从核心算法上下功夫,但目前我并没有更好的想法。

PSP表格记录下你在程序的各个模块上实际花费的时间

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划
· Estimate · 估计这个任务需要多少时间 30 22
Development 开发
· Analysis · 需求分析 (包括学习新技术) 30 25
· Design Spec · 生成设计文档 30 40
· Design Review · 设计复审 (和同事审核设计文档) 30 35
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 30 25
· Design · 具体设计 30 40
· Coding · 具体编码 100 120
· Code Review · 代码复审 100 70
· Test · 测试(自我测试,修改代码,提交修改) 100 120
Reporting 报告
· Test Report · 测试报告 60 80
· Size Measurement · 计算工作量 40 30
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 100 120
合计 680 727
注:以上时间为大致估计时间。

个人总结

这次项目有很多都是我第一次接触,比如第一次在Visual Studio Community做项目开发、第一次使用Github做代码管理及第一次使用C++做项目。虽然这些第一次让我有些措手不及,但是项目还是在磕磕碰碰中完成了。虽然没有去做附加题我觉得有些遗憾,但是让我对Visual Studio Community和Github有了新的认识对我来说还是有比较大的收获的。再次我对于数独了解得并不是很多,也没有真正求解过数独棋盘,所以在核心算法上采用的是暴力计算的方法,虽然完成的基本任务,但是我还是觉得还有很大的提升空间。这个项目也使我认识到我能力还是有待提高的,对于项目设计,程序的测试纠错还是比较薄弱。在这个项目中我发现我的程序有一个很明显的bug:就是在程序输入出错的情况下我有设计让用户手动输入一个整数保证数独矩阵的生成,如果手动输入的值不是整数而是其他类型的数据则整个程序就会出错。但是我相信随着我的学习的深入,我会有更好的解决办法的,能力也会随之提升。也希望报告和程序中有什么写得不好的地方,请批改老师见谅。

posted on 2017-10-06 20:30  Maeder  阅读(241)  评论(0编辑  收藏  举报