zoukankan      html  css  js  c++  java
  • 软工个人项目-求交点数目

    这是一篇课程博客。

    Q A
    这个作业属于哪个课程 2020春北航计算机学院软件工程(罗杰 任健)
    这个作业的要求在哪里 个人项目作业
    我在这个课程的目标是 系统地学习软件工程理论知识与实践方案
    这个作业在哪个具体方面帮助我实现目标 体验PSP流程;尝试编写高质量软件
    作业代码在哪里 https://github.com/Mistariano/intersect
    PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
    Planning 计划 20min 20min
    · Estimate 估计这个任务需要多少时间 5min 5min
    Development 开发 3h 2.5h
    · Analysis 需求分析 (包括学习新技术) 10min 40min
    · Design Spec 生成设计文档 10min 10min
    · Design Review 设计复审 (和同事审核设计文档) 0min 0min
    · Coding Standard 代码规范 (为目前的开发制定合适的规范) 10min 0min
    · Design 具体设计 30min 50min
    · Coding 具体编码 3h 2h
    · Code Review 代码复审 0min 0min
    · Test 测试(自我测试,修改代码,提交修改) 40min 10min
    Reporting 报告 40min 20min
    · Test Report 测试报告 20min 0min
    · Size Measurement 计算工作量 6h 4h
    · Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 10min 10min
    合计 15h15min 11h15min

    回顾

    这次作业和eccv、美赛撞个满怀,时间过于紧张(作业3月3号发布,但直到3月6日早上5点前还在改准备投出的论文、完善说明和实验。3月6日8点又放出了美赛题目,几乎没有休息就又全身心投入比赛,一直到3月10日临近中午才全部完成。加上下午的课从2点上到4点,实际留给最后编码的时间只有3个小时……加上这周中间几个小时段的穿插,这次作业总共投入了不到10个小时的时间,和之前的投入实在没法相提并论。)

    在任务规划时已经把各方面的时限压到最低(见上面的PSP,对于一次大作业来说规划的工作强度接近个人极限了),因此完成质量实在难以保持,做的不太好,很多地方没有完善。

    虽然狼狈如此,这次作业依旧有很多可以总结的东西,我觉得这就是软工方法论中最有意思的地方之一:总是有可以总结的经验;总是有机会学习所有经验。

    理解题目

    要求平面内交点数量,存在多线共点

    难点主要在于处理浮点数精度和处理极大的输入规模(50w条线,500w个点,复杂度相对于交点应该不能超过MlogM)

    考虑引入分数类处理,使用搜索树(STL set,红黑树)借助自己实现的compare维护相近点查找

    具体细节

    首先根据输入的点将直线表示为一般形式(y=ax+b)(需要注意直线与y平行的特殊情况,后面求交点一步中单独处理),然后对于所有直线,计算其与其他直线的交点位置,这一步需要N^2/2的开销,N是线的数量

    中间所有计算结果使用分数类维护,记录分子和分母。这里有两个trick点,

    1. 对每个分数用fp32记录一个粗略的估计值rough,这样对于两个分数的比大小我们可以由粗到细的实现:先比较rough的结果,如果差值在浮点精度epsilon以外就可以直接得到比较结果,只有精度无法判断两者是否相等时才比较分数结果
    2. 对于分数的比较,比较前显然需要一次约分,这里约分采用lazy策略处理,对每个分数标记simplfied,只有在需要约分时才对未约分过的分数约分。这样在搜索树中找到插入位置前,分数类的数值比较不会带来过多的额外开销。

    代码设计

    考虑到功能需求以数值计算为主,没有特别强烈的扩展性需求及代码复用动机,且对性能要求较高,选择采用面向过程的范式进行设计(只有在涉及容器调用及编写Compare函数类时才涉及到一小部分面向对象和泛型特性)。

    对于面向过程程序,函数的封装粒度是尤其需要控制,一个适当的封装粒度不仅不会增加工程复杂性,而且会很大地简化测试过程。这里我的设计采用三层:

    • 外层逻辑流控制,输入/输出。
    • 分数处理过程,包括分数的预处理、分数运算、分数比较等,见代码fractions.h
    • 查找树的封装。之前由于考虑过如果时间允许,使用自己定制的查找树替换stl的红黑树,因此做了一层adaptor,可惜最后时间不够没有进一步实现自己的查找树。这部分见代码trees.h

    其中trees.h中,关于比较的一段逻辑比较重要,因此摘录部分代码

    // 这个类就是两个分数的具体比较过程。首先根据fp32的低精度值比较,若数值接近再细致比较。
    int _cmp_fraction(fraction_t &a, fraction_t &b) {
        if (a.rough - b.rough > EPS) {
            // a>b
            return 1;
        }
        if (b.rough - a.rough > EPS) {
            // a<b
            return -1;
        }
        if (!a.simplified) {
            int64_t gcd1 = gcd(a.top_abs, a.bottom_abs);
            a.top_abs %= gcd1;
            a.bottom_abs %= gcd1;
            a.rough = (float) a.sign * (float) a.top_abs / (float) a.bottom_abs;
            a.simplified = true;
        }
        if (!b.simplified) {
            int64_t gcd1 = gcd(b.top_abs, b.bottom_abs);
            b.top_abs %= gcd1;
            b.bottom_abs %= gcd1;
            b.rough = (float) b.sign * (float) b.top_abs / (float) b.bottom_abs;
            b.simplified = true;
        }
        int64_t a_top = a.top_abs * a.sign;
        int64_t b_top = b.top_abs * b.sign;
    
        if (a_top == b_top && a.bottom_abs == b.bottom_abs) {
            return 0;
        }
        // TODO
        // 很遗憾这里最后还差一个分支没有实现完。程序运行到这里的时候,已经可以确定两个分数数值接近但不相等了,因此直接对分数类做差判断分子符号即可。
        // 即:判断a_top*b_bottom - a_bottom - b*top的符号
        return 1;
    }
    
    // 由于每个点有两个分数数值(x和y),因此将x作为第一索引、y作为第二索引,满足全序
    int _cmp_before_ins(int f, int i) {
        // insert node i to sub-tree whose root is t
    
        auto &fnode = tree_pool[f];
        auto &cur = tree_pool[i];
        // index1: compare x fraction
        int res_x = _cmp_fraction(fnode.x, cur.x);
        if (res_x == 0) {
            int res_y = _cmp_fraction(fnode.y, cur.y);
            return res_y;
        }
        return res_x;
    }
    
    // 比较函数类,通过重载调用运算符使这个类的对象可以像函数一样被调用。
    // 这里由于使用了一块内存池tree_pool管理所有结点,因此通过定义索引排序规则实现间接排序
    struct Compare {
        int operator()(int a, int b) const {
            return _cmp_before_ins(a, b) >= 0 ? 0 : 1;
        }
    };
    
    // 使用Compare作为排序规则的交点集。最后这个集合的size就是需要的答案
    std::set<int, Compare> set;
    
    // 结点插入的adaptor。如果之后实现了其他二叉树,修改这里即可
    void insert_node(int i) {
        auto &cur = tree_pool[i];
        int before = set.size();
        set.insert(i);
        if (set.size() == before) {
            free_node();
        }
    }
    
    
    

    CMake与VS2019

    由于VS2019引入了对CMake的支持,这次作业在规划时使用个人更熟悉的CMake进行编译管理,因此这份代码可以拜托对VS的限制,在任何平台都可以几乎零改动完成编译。

    但很遗憾规划任务时没有注意到VS的CMake支持并不完善,像单元测试生成、代码质量分析等工具的使用方式和传统VS sln解决方案并不相同,而个人对这些特性本身就不熟悉。因此在追求快速开发、于Jetbrains Clion上完成代码主体后,尝试在VS上实现课程组要求的分析任务时严重受挫,最终没有完成。

    即使如此,我也尽可能注意了代码质量的控制,包括对代码风格的约束(虽然没有来得及修改格式配置,很多风格与课程推荐有出入)、必要注释的添加和一些基础的黑箱测试(平行线、多点共线、与坐标轴平行的线、交点十分接近的线、构造大质数样例测数值溢出风险等)。很可惜这些测试没有办法体现在代码里。

    总结

    虽然是很狼狈的一次作业,但回顾一下,我对自己在相当短的时间内走完了分析、设计、测试和总结的PSP流程总体还是满意的。由于这次不可抗力太多而导致的投入严重不足,我对作业分数有相当的心理准备,但至少这是一次高压下的自我操练,我依然很珍惜并感谢这次开发经历。

    希望这周的结对编程好好表现 ,不要坑害队友啦。

  • 相关阅读:
    测试sql语句性能,提高执行效率
    js积累
    如何提高AJAX客户端响应速度
    视频代码
    网页视频播放器收集
    WinForm软件开机自动启动详细方法
    JS时间格式化函数
    (转)CSS+DIV float 定位
    CSS+DIV 布局三种定位方式
    CSS+DIV布局初练—DIV元素必须成对出现?
  • 原文地址:https://www.cnblogs.com/MisTariano/p/12457756.html
Copyright © 2011-2022 走看看