• 北航软工个人项目作业


    个人项目作业-求图形交点个数

    项目 内容
    本作业属于北航 2020 年春软件工程 博客园班级连接
    本作业是本课程个人项目作业 作业要求
    我在这个课程的目标是 收获团队项目开发经验,提高自己的软件开发水平
    这个作业在哪个具体方面帮助我实现目标 体验软件开发的 Workflow
    项目代码 Github 仓库

    解题思路

    根据需求描述,我们不难得到所需软件的运行流程,总体而言分为三步:

    • 解析命令行参数,获取输入文件与输出文件的路径
    • 从输入文件中获取输入,并对图形参数进行解析,存储于相应的数据结构中
    • 求解图形之间的交点个数,并输出

    第一步是命令行交互软件的基本操作,不再赘述。

    第二步的关键在于设计存储图形所需的数据结构。考虑图形有两种:直线与圆,且直线的输入数据是两点式,圆的输入数据是圆心-半径式,同时点需要两个参数来标识,半径需要一个参数来标识,因此每个点用数对来存储,每条直线用两个点来表示,每个圆用一个点和一个半径来表示。

    第三步是最复杂的。图形类型有两种,因此有(C^2_3)种情况需要纳入考虑范围内。根据数学知识,继续划分如下:

    • 直线与直线
      • 平行:交点个数 0
      • 同一条直线:交点个数无限
      • 相交:交点个数 1
    • 直线与圆
      • 相离:交点个数 0
      • 相切:交点个数 1
      • 相交:交点个数 2
    • 圆与圆
      • 相离:交点个数 0
      • 相切:交点个数 1
      • 相交:交点个数 2
      • 内含:交点个数 0

    每一种情况都有完整的初等解法,参见 Paul Bourke 先生的文章

    设计

    数据结构

    同上述提到的数据结构设计。CPP 代码如下

    using Point  = std::pair<int32_t, int32_t>;
    using Line   = std::pair<Point, Point>;
    using Circle = std::pair<Point, float>;
    

    对于交点的存储,如果只有直线,那么可以用两个整数来表示有理数的方法实现,不存在精度的问题。但是除了直线以外还有圆,引入了无理数,因此浮点数是有必要的。CPP 代码如下。

    struct IntersectPoint
    {
        float first;
        float second;
    
        IntersectPoint(float first, float second) : first(first), second(second) {}
    
        bool operator < (const IntersectPoint& rhs) const
        {
            return first - rhs.first < -eps || (!(rhs.first - first < -eps) && second - rhs.second < -eps);
        }
        friend bool operator < (IntersectPoint& left, IntersectPoint& right)
        {
            return left.operator<(right);
        }
    };
    

    可以发现,我没有选择使用 STL 内建数据结构,而是使用自定义类,原因在于精度。由于浮点数的不准确性,两个数学上相等的值在计算机中可能会因为运算的原因产生误差,此时需要使其在一定程度上容许误差的存在,因此不能容许误差的 STL 内建数据结构不是一个好的选择。

    复杂度分析

    对于需求中的第三步操作,我的策略如下:每个图形都与其他图形计算一次交点,存储交点并去重。不难发现,时间复杂度与空间复杂度均为(Theta(n^2))

    实际上,这个复杂度是难以接受的,尤其是对于 50W 的输入数据量而言。在测试时,空间占用更是一度达到了 32GB。由于时间关系,我暂且还未找到更佳的处理策略。

    代码实现

    代码组织

    共有 5 个主要文件,其中 main.cpp 与 option.hpp 主要内容是用户交互与 benchmark,intersect.cpp 与 intersect.hpp 主要内容是主要功能的实现,intersect_test.cpp 的内容则是测试相关。

    IntersectProject
    └── src
        ├── main.cpp
        ├── intersect.cpp
    └── include
        ├── intersect.hpp
        ├── option.hpp
    └── test
        └── intersect_test.cpp
    

    在 intersect.cpp 中,函数的关系大致如下

    calculate_intersect_points - 计算图形之间的交点个数
    ├── calculate_line_circle_intersect_points - 计算一条直线和一个圆之间的交点个数
    ├── calculate_line_intersect_points - 计算两条直线之间的交点个数
    └── calculate_circle_intersect_points - 计算两个圆之间的交点个数
    

    可以看出,只要功能函数的组织和需求分析是相吻合的。

    不同情况下的返回值处理

    正如上面提到的,两个图形在不同的位置有着不同的相对关系,从而影响着交点的个数。

    但总的来说,分为两种,0 个和 2 个(相切时,交点相同)。这种情况下,我选择 C++17 引入的 std::optional,这是一个 Sum Type,可以优雅地处理不同返回值问题,不需要额外引入 flag 变量以标志返回值内容。

    测试

    对于两个图形位置的每个可能情况,都有相应的一个测试点。代码覆盖率如下:

    (使用 OpenCPPCoverage 生成,由于测试问题,与用户交互的 main.cpp 和 option.hpp 基本没有覆盖,只覆盖了功能实现的 intersect.cpp)

    代码质量分析

    如下

    实际上,我的 Visual Studio 是配以 Jetbrains 的 Resharper C++ 使用的,Resharper C++ 可以帮助我调用 clang-tidy 及时地对代码风格与质量进行提醒。

    性能改进

    初始时,我选择 std::set 作为交点的存储容器。经过 profile 之后,我发现热点在于 set::insert,它的调用次数非常多,而且每次调用的花销非常高。因此,我选择了 std::vector 作为容器,在处理完毕后再进行排序去重操作。对于 1000 级别的数据量而言,性能提高了约三倍。但是,目前的性能热点仍然在于排序去重操作,这是我目前采取的处理策略所不能避免的。

    事后总结

    由于本次任务的需求是确定的,没有太多需要揣测的地方,因此需求分析花费的时间比较少。

    而本次任务花费时间最多的阶段是设计阶段,主要在于寻找一个不需要复杂运算且具有足够精度的方法。在数学上,可以使用反三角函数来轻松解决这个问题,但是在计算机中,反三角函数是一个复杂的运算过程,且精度一般。我尝试使用 Mathmatica 辅助求解,但给出的计算公式十分复杂,没能充分利用图形本身的特征进行简化。最后,我找到了 Paul Bourke 先生的文章,方法很简洁,计算过程也适合于计算机。

    PSP 表格

    PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
    Planning 计划
    · Estimate · 估计这个任务需要多少时间 5 5
    Development 开发
    · Analysis · 需求分析 (包括学习新技术) 15 15
    · Design Spec · 生成设计文档 10 10
    · Design Review · 设计复审 (和同事审核设计文档) 0 0
    · Coding Standard · 代码规范 (为目前的开发制定合适的规范) 0 0
    · Design · 具体设计 30 90
    · Coding · 具体编码 60 60
    · Code Review · 代码复审 10 10
    · Test · 测试(自我测试,修改代码,提交修改) 60 60
    Reporting 报告
    · Test Report · 测试报告 10 10
    · Size Measurement · 计算工作量 10 10
    · Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 30
    合计 240 300
  • 相关阅读:
    Burp
    SQL注入
    网络安全没有“银弹”
    Centos7
    虚拟机的使用流程
    虚拟机安装流程
    nmap指令
    UDP 服务器和客户端实例,实现2个客户端通过UDP服务器打洞穿透
    c++ win32下窗口的最小化到托盘以及还原
    基于百度OCR的图片文字识别
  • 原文地址:https://www.cnblogs.com/btapple/p/12454131.html
Copyright © 2020-2023  润新知