zoukankan      html  css  js  c++  java
  • 2020软件工程作业03

    这个作业属于哪个课程 https://edu.cnblogs.com/campus/zswxy/software-engineering-2017-1/
    这个作业要求在哪里 https://edu.cnblogs.com/campus/zswxy/software-engineering-2017-1/homework/10494
    这个作业的目标 Sudoku
    作业正文
    其他参考文献 giyf

    1、Github项目地址

    https://github.com/iriszero48/20177713


    2、PSP表格

    PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
    Planning 计划 5 5
    Estimate 估计这个任务需要多少时间 5 5
    Development 开发 950 1780
    Analysis 需求分析 (包括学习新技术) 10 10
    Design Spec 生成设计文档 60 30
    Design Review 设计复审 5 5
    Coding Standard 代码规范 (为目前的开发制定合适的规范) 0 0
    Design 具体设计 30 10
    Coding 具体编码 720 1560
    Code Review 代码复审 5 5
    Test 测试(自我测试,修改代码,提交修改) 120 160
    Reporting 报告 80 570
    Test Repor 测试报告 20 30
    Size Measurement 计算工作量 0 0
    Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 60 540
    合计 1035 2355

    3、思路描述

    知道的方法有几种

    • 回溯(蛮力搜索)
    • 随机搜索(模拟退火算法/遗传算法/禁忌搜索算法/爬山算法/蚁群算法/霍普菲尔德神经网络)
    • amb
    • 精确覆盖问题(舞蹈链算法)
    • 线性规划

    为了偷懒选择线性规划实现数独求解


    4、设计实现过程

    建立三个项目

    Sudoku

    数独的实现

    类图

    Sudoku类图

    代码图

    Sudoku代码图

    • InitializeWithFilePath用于从文件初始化

    • InitializeWithArray用于从array初始化

    • Solve用于求解

      根据m,n添加约束关系然后塞进Microsoft.Solver.Foundation线性规划库中求解

    • ToString用于将array转换成string

    Sudoku类使用流程

    1. 构造函数 Sudoku
    2. 初始化 InitializeWithFilePath/InitializeWithArray
    3. 求解 Solve
    4. 转成string ToString

    Launcher

    整个程序的入口,见输入的参数转化成键值对进行解析并调用数独

    UnitTest

    使用Microsoft.VisualStudio.QualityTools.UnitTestFramework进行单元测试


    5、性能改进

    使用参数-m 9 -n 20 -i Z:input.txt -o Z:o.txt

    vs性能探察器

    vs性能探察器调用树

    dotTrace allcall

    dotTrace overview

    ConcurrencyVisualizer使用率

    ConcurrencyVisualizer线程

    绝大部分的时间花在了Microsoft.Solver.Foundation的Solver上,无法优化

    可以考虑对每个棋盘进行并行求解

    使用OpenMP2.0对for进行并行,每次调度两个线程

    #pragma omp parallel for schedule(dynamic, 2)
    for (auto i = 0; i < maps->Length; ++i)
    {
        ...
    }
    

    在20个棋盘时会增加100~200ms的开销

    ConcurrencyVisualizer使用率20个多线程

    ConcurrencyVisualizer线程20个多线程

    在100个棋盘时单线程 2015ms

    ConcurrencyVisualizer使用率100个单线程

    ConcurrencyVisualizer线程100个单线程

    在100个棋盘时多线程 988ms

    ConcurrencyVisualizer使用率100个多线程

    ConcurrencyVisualizer线程100个多线程

    在100个棋盘时多线程 内存分析

    dotMemory


    6、静态分析、单元测试、运行

    静态分析

    数独9x9测试

    [TestMethod]
    void Sudoku9x9()
    {
        const Maps_ mp
        {
            {
                { 0,0,5, 3,0,0, 0,0,0 },
                { 8,0,0, 0,0,0, 0,2,0 },
                { 0,7,0, 0,1,0, 5,0,0 },
    
                { 4,0,0, 0,0,5, 3,0,0 },
                { 0,1,0, 0,7,0, 0,0,6 },
                { 0,0,3, 2,0,0, 0,8,0 },
    
                { 0,6,0, 5,0,0, 0,0,9 },
                { 0,0,4, 0,0,0, 0,3,0 },
                { 0,0,0, 0,0,9, 7,0,0 }
            },
                {
                { 0,0,0, 0,0,0, 0,0,0 },
                { 0,0,0, 0,0,3, 0,8,5 },
                { 0,0,1, 0,2,0, 0,0,0 },
    
                { 0,0,0, 5,0,7, 0,0,0 },
                { 0,0,4, 0,0,0, 1,0,0 },
                { 0,9,0, 0,0,0, 0,0,0 },
    
                { 5,0,0, 0,0,0, 0,7,3 },
                { 0,0,2, 0,1,0, 0,0,0 },
                { 0,0,0, 0,4,0, 0,0,9 }
            },
            {
                { 1,1,0, 0,0,0, 0,0,0 },
                { 0,0,0, 0,0,0, 0,0,0 },
                { 0,0,0, 0,0,0, 0,0,0 },
    
                { 0,0,0, 0,0,0, 0,0,0 },
                { 0,0,0, 0,0,0, 0,0,0 },
                { 0,0,0, 0,0,0, 0,0,0 },
    
                { 0,0,0, 0,0,0, 0,0,0 },
                { 0,0,0, 0,0,0, 0,0,0 },
                { 0,0,0, 0,0,0, 0,0,0 }
            }
        };
    
        auto s = gcnew Sudoku(9, 3);
        s->InitializeWithArray(ToMaps(mp));
        Assert::AreEqual(R"(1 4 5 3 2 7 6 9 8
    8 3 9 6 5 4 1 2 7
    6 7 2 9 1 8 5 4 3
    4 9 6 1 8 5 3 7 2
    2 1 8 4 7 3 9 5 6
    7 5 3 2 9 6 4 8 1
    3 6 7 5 4 2 8 1 9
    9 8 4 7 6 1 2 3 5
    5 2 1 8 3 9 7 6 4
    
    9 8 7 6 5 4 3 2 1
    2 4 6 1 7 3 9 8 5
    3 5 1 9 2 8 7 4 6
    1 2 8 5 3 7 6 9 4
    6 3 4 8 9 2 1 5 7
    7 9 5 4 6 1 8 3 2
    5 1 9 2 8 6 4 7 3
    4 7 2 3 1 9 5 6 8
    8 6 3 7 4 5 2 1 9
    
    Unsolvable!
    
    )", Sudoku::ToString(s->Solve()));
    }
    

    从程序基本的运行和非法值方面设计单元测试

    • 数独3x3
    • 数独4x4
    • 数独5x5
    • 数独6x6
    • 数独7x7
    • 数独8x8
    • 数独9x9
    • 从文件输入数据
    • M在[3,9]外
    • N小于0
    • N的大小与输入的数据不匹配
    • M的大小与输入的数据不匹配

    测试结果

    单元测试

    运行结果

    • 数独3x3

    数独3x3

    • 数独4x4

    数独4x4

    • 数独5x5

    数独5x5

    • 数独6x6

    数独6x6

    • 数独7x7

    数独7x7

    • 数独8x8

    数独8x8

    • 数独9x9

    数独9x9


    6、异常处理

    数独有什么异常应该是无法继续跑,所以直接在main使用try...catch

    try
    {
        ...
    }
    catch (System::Exception^ exception)
    {
        fprintf(stderr, "Usage:
      %s -m 宫格阶级 -n 待解答盘面数目 -i 指定输入文件 -o 指定程序的输出文件
    ", argv[0]);
        System::Console::Error->WriteLine(exception->ToString());
        return EXIT_FAILURE;
    }
    

    参数不足8个

    if (argc != 9)
    {
        throw gcnew System::Exception("parameter error");
    }
    

    初始化数独,检查mn是否有效

    if (m < 3 || m > 9)
    {
        throw gcnew System::Exception("M must be 3 to 9");
    }
    if (n < 0)
    {
        throw gcnew System::Exception("N must be greater than -1");
    }
    

    对应单元测试,M应该是[3,9],这里初始化为2,所以抛出异常"M must be 3 to 9"

    [TestMethod]
    void MOutOfRange()
    {
        try
        {
            gcnew Sudoku(10, 1);
        }
        catch (System::Exception^ exception)
        {
            Assert::IsTrue(exception->ToString()->Contains("M must be 3 to 9"));
        }
        try
        {
            gcnew Sudoku(2, 1);
        }
        catch (System::Exception^ exception)
        {
            Assert::IsTrue(exception->ToString()->Contains("M must be 3 to 9"));
        }
    }
    

    检查mn是否与数据大小相匹配

    if (maps->Length != n)
    {
        throw gcnew System::Exception("N size no matches");
    }
    for (auto i = 0; i < maps->Length; ++i)
    {
        if (maps[i]->Length != m)
        {
            throw gcnew System::Exception("M size no matches");
        }
        for (auto j = 0; j < m; ++j)
        {
            if (maps[i][j]->Length != m)
            {
                throw gcnew System::Exception("M size no matches");
            }
        }
    }
    

    对应单元测试,mp为9x9的数独,保存时使用Substring去掉了第一个数字,变成了不规则数组,不满足每行每列都是M个的要求,所以抛出异常"M size no matches"

    [TestMethod]
    void MSizeNoMatch()
    {
        const Maps_ mp
        {
            {
                { 0,0,5, 3,0,0, 0,0,0 },
                { 8,0,0, 0,0,0, 0,2,0 },
                { 0,7,0, 0,1,0, 5,0,0 },
    
                { 4,0,0, 0,0,5, 3,0,0 },
                { 0,1,0, 0,7,0, 0,0,6 },
                { 0,0,3, 2,0,0, 0,8,0 },
    
                { 0,6,0, 5,0,0, 0,0,9 },
                { 0,0,4, 0,0,0, 0,3,0 },
                { 0,0,0, 0,0,9, 7,0,0 }
            }
        };
        const auto filename = gcnew System::String("FileInput.txt");
        System::IO::File::WriteAllText(filename, Sudoku::ToString(ToMaps(mp))->Substring(2));
        auto sudokuFile = gcnew Sudoku(9, 1);
        try
        {
            sudokuFile->InitializeWithFilePath(filename);
        }
        catch (System::Exception^ exception)
        {
            Assert::IsTrue(exception->ToString()->Contains("M size no matches"));
        }
    
        System::IO::File::Delete(filename);
    }
    

    7、代码说明

    数独的核心是Sudoku::Maps^ Sudoku::Solve()函数

    流程图

    流程图

    代码

    Sudoku::Maps^ Sudoku::Solve()
    {
        using namespace Microsoft::SolverFoundation::Solvers;
    
        // 创建一个三维数组存放结果
        auto res = gcnew Maps(maps->Length);
        for (auto i = 0; i < maps->Length; ++i)
        {
            res[i] = gcnew Map(maps[i]->Length);
            for (auto j = 0; j < maps[i]->Length; ++j)
            {
                res[i][j] = gcnew array<int>(maps[i][j]->Length);
            }
        }
    
        // 并行遍历每个盘面,同时调度两个线程
        #pragma omp parallel for schedule(dynamic, 2)
        for (auto i = 0; i < maps->Length; ++i)
        {
            // 初始化
            auto map = maps[i];
            auto s = ConstraintSystem::CreateSolver();
            const auto z = s->CreateIntegerInterval(1, maps[i]->Length);
            auto lp = s->CreateVariableArray(z, "n", maps[i]->Length, maps[i]->Length);
    
            // 为每行和已知条件添加约束
            for (auto row = 0; row < maps[i]->Length; ++row)
            {
                for (auto col = 0; col < maps[i]->Length; ++col)
                {
                    if (map[row][col] > 0)
                    {
                        s->AddConstraints(s->Equal(map[row][col], lp[row][col]));
                    }
                }
                s->AddConstraints(s->Unequal(GetSlice(lp, maps[i]->Length, row, row, 0, maps[i]->Length - 1)));
            }
    
            // 为每列添加约束
            for (auto col = 0; col < maps[i]->Length; ++col)
            {
                s->AddConstraints(s->Unequal(GetSlice(lp, maps[i]->Length, 0, maps[i]->Length - 1, col, col)));
            }
    
            // 为不同盘面阶数设置宫格大小
            auto stepRow = 0;
            auto stepCol = 0;
            switch (maps[i]->Length)
            {
            case 4:
                stepRow = 2;
                stepCol = 2;
                break;
            case 6:
                stepRow = 2;
                stepCol = 3;
                break;
            case 8:
                stepRow = 4;
                stepCol = 2;
                break;
            case 9:
                stepRow = 3;
                stepCol = 3;
                break;
            default:;
            }
    
            // 如果当前盘面阶数存在宫格,则为每个宫格添加约束
            if (stepRow != 0 && stepCol != 0)
            {
                for (auto row = 0; row < maps[i]->Length; row += stepRow)
                {
                    for (auto col = 0; col < maps[i]->Length; col += stepCol)
                    {
                        s->AddConstraints(
                            s->Unequal(
                                GetSlice(lp, stepCol * stepRow, row, row + stepRow - 1, col, col + stepCol - 1)));
                    }
                }
            }
    
            // 求解
            auto sol = s->Solve();
            try
            {
                for (auto row = 0; row < maps[i]->Length; ++row)
                {
                    for (auto col = 0; col < maps[i]->Length; ++col)
                    {
                        res[i][row][col] = sol->GetIntegerValue(lp[row][col]);
                    }
                }
            }
            catch (System::Exception^) // 无解情况返回nullptr
            {
                res[i] = nullptr;
            }
        }
        return res;
    }
    

    8、心路历程与收获

    刚开始要求写数独想着用微软的Microsoft.Solver.Foundation线性规划库偷懒,然后看见要求“使用C++或者Java语言实现”,就决定使用C++/CLI方言,因为Microsoft.Solver.Foundation是基于.NET Framework的(后面老师说不限定语言,但是已经开工了,就继续C++/CLI了),C++/CLI能直接用.NET Framework的库,顺便还能把FSharp的核心库也拿来用用,写着写着,发现C++/CLI和F#交互太麻烦了,把函数作为参数传递给F#折腾了几个小时,最后决定写个FSharpFuncUtil来解决这个问题,FSharpFuncUtil将System.Func<'a,'b>转换成FSharpFunc<'a,'b>,System.Func可以gcnew出来,自定义函数也能定义个ref class塞个静态函数gcnew解决问题,原本还想整个类似于std::Bind1st一样的函数用于FSharpFunc/System.Func,想到万一是个大坑就没整了,最后放上这个程序真正的一行核心代码(x

    maps = ArrayModule::Take(n, ArrayModule::Map(FSharpFuncUtil::FSharpFuncUtil::ToFSharpFunc(gcnew System::Func<array<int>^, array<array<int>^>^>(Func::ChunkBySize)), ArrayModule::ChunkBySize(m * m, ArrayModule::Map(FSharpFuncUtil::FSharpFuncUtil::ToFSharpFunc(gcnew System::Func<System::String^, int>(System::Int32::Parse)),System::IO::File::ReadAllText(path)->Replace("
    ", " ")->Replace("
    ", " ")->Split(mapSp, System::StringSplitOptions::RemoveEmptyEntries)))));
    

  • 相关阅读:
    Java学习之集合(HashSet)
    Java学习之集合(LinkedList链表集合)
    Java学习之集合(List接口)
    Java学习之集合(Collection接口)
    【Spring Session】和 Redis 结合实现 Session 共享
    【NodeJS】nvm
    【Html JS】使用问题记录
    【VUE】使用问题记录
    【RabbitMQ】显示耗时处理进度
    【CentOS7】开发环境配置
  • 原文地址:https://www.cnblogs.com/iriszero/p/12582497.html
Copyright © 2011-2022 走看看