• 软件工程基础 第3次个人作业


    一点说明

    这篇博客是软件工程基础(罗杰、任建)的第三次课程作业(个人项目作业)

    项目 内容
    这个作业属于哪个课程 软件工程基础(罗杰,任建)
    这个作业的要求在哪里 作业要求的链接
    我在这个课程的目标是 提升对软件工程的宏观和微观的全面认识,并加以实践
    作业在哪些方面帮我实现目标 亲身实践个人项目开发的完整流程
    我的教学班级 006
    我的GitHub项目地址 https://github.com/SnowOnVolcano/IntersectProject.git

    PSP表格

    在开始实现程序之前,在下述 PSP 表格记录下你估计将在程序的各个模块的开发上耗费的时间。(0.5')

    在你实现完程序之后,在下述 PSP 表格记录下你在程序的各个模块上实际花费的时间。(0.5')

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

    基本需求

    1. 解题思路

    解题思路描述。即刚开始拿到题目后,如何思考,如何找资料的过程。(3')

    • 思路一

      读完问题的第一思路,是利用高中学到的 一般方程法 进行交点的求解:

      [设直线方程为 ax+by+c=0, 已知直线过两点(x_1,y_1)和(x_2,y_2),则有\ egin{cases}ax_1+by_1+c=0\ax_2+by_2+c=0end{cases}implies egin{cases}a=y_1-y_2\b=x_2-x_1\c=x_1y_2-x_2y_1end{cases}\ 设两直线分别为 a_1x+b_1y+c_1=0 和 a_2x+b_2y+c_2=0,联立两直线方程,得\ egin{cases}a_1x+b_1y+c_1=0\a_2x+b_2y+c_2=0end{cases}implies egin{cases}x=frac{b_1c_2-b_2c_1}{a_1b_2-a_2b_1}\y=frac{a_2c_1-a_1c_2}{a_1b_2-a_2b_1}end{cases}implies egin{cases}x=frac{b_1c_2-b_2c_1}{D}\y=frac{a_2c_1-a_1c_2}{D}end{cases},其中 D=a_1b_2-a_2b_1 ]

      这样的解法有几个好处:

      • 可以较好地处理特殊情况,比如,可以通过判断 D 是否为 0,直接判断两直线是否平行;
      • 这种方法直接计算出交点的坐标,这样可以设置一个 set 用来存放已得到的交点,直接在存入时进行除重,最终结果输出 set 的大小即可;

      当然,其缺点也很明显:

      • 计算简单粗暴,对于具有 N 条直线的样本,需要进行 (C_N^2) 次计算,时间复杂度为 (O(n^2))
      • 如果简单地使用 set<(x,y)> 进行的方式进行存储,则需要进行浮点运算,这既会带来精度的损失,也会加长运行时间。
    • 思路二

      如果不采用直接计算的方法,如何得出交点个数呢?我想到的另一个方法是,做减法

      如果任意的两条直线都相交且任意的三条直线不交于同一点,那么对于具有 N 条直线的样本,交点的个数为 (C_N^2),对一般情况就有,

      [总交点数=C_N^2-平行直线对的数目-sum_{所有的交点}{(同一交点的直线数目-2)} ]

      但是,仔细想想,这种方法似乎并不比思路简单,因为我没有想到好的算法去计算同一交点的直线数目……

    • 思路三

      想不到好的算法,我最后只能决定使用直接计算的方法,但是我想其实思路一的方法还可以简单地优化一下细节,减小精度损失,有两个方法,

      • 在存放交点时,交点的纵横坐标的分子分母分开存储,这样可以规避浮点运算,从而保证精度;
      • set 的排序函数进行重载,设置一定的精度范围,这样可以一定程度地减小精度损失,但是无法从根本上避免。

      我最终选择了后者,以平衡精度和时间复杂度。

    2. 实现过程

    设计实现过程。设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?单元测试是怎么设计的?(4')

    • 结构设计(2个结构体)
      • Point:表示点 ((x,y)),其中 Point.xPoint.y 分别表示点的纵横坐标;
      • Line:表示直线 (ax+by+c=0),其中 Line.aLine.bLine.c 分别对应直线的三个参数;
    • 函数实现(2+1+1个函数)
      • PointLine 的构造函数,计算两直线交点的函数 calLineLineIst(...) ,主函数;

      • 各函数关系如下,

        img
    • 单元测试

      我主要进行了三个方面的单元测试:

      • 构造函数是否能正确初始化:包括参数的传递和计算;
      • 交点计算函数是否能覆盖所有情况:包括多线一点、平行的情况,以及直线与坐标轴平行等情况;
      • 交点集合的精确度是否能保证:重点测试了以下两种情况,1)在交点相差较小的情况下,是否能够区分开不同的交点;2)在交点的小数点位数较多时,是否能保证交点不重复。

      以下是我的一些单元测试的测试点的图形示意:

    3. 性能改进

    记录在改进程序性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由 VS 2019 的性能分析工具自动生成),并展示你程序中消耗最大的函数。(3')

    • 在改进之前,我进行了一次程序性能的测试,我发现,最费时的操作是 set 的插入时,遍历红黑树的过程。反而计算交点的函数没有花费太多的时间。
    • 但是,在尝试了非红黑树的集合之后,我发现有时候正确性得不到保证,所以我最后还是选择了保持原有的 set。
    • 其他的优化都是小修小补了,比如,
      • 优化了代码结构,将点和直线的初始化函数放入了结构体内;
      • 除了最后的计算,中间过程使用整型,而非浮点型
    • 优化后的图如下
    img
    • 程序中消耗最大的函数如下
    img

    4. 代码说明

    代码说明。展示出项目关键代码,并解释思路与注释说明。(3')

    • 计算直线与直线交点(思路请见代码注释)
      // calculate the intersections of two lines
      static void calLineLineIst(Line& line1, Line& line2) {
      	int D;
          D = line1.a * line2.b - line2.a * line1.b;
      	switch (D)
      	{
      	case 0:	// parallel
      		break;
      	default:
      		//	line1: a1*x+b1*x+c1=0, line2: a2*x+b2*x+c2=0
      		//	==> x=(b1*c2-b2*c1)/(a1*b2-a2*b1),
      		//		y=(a2*c1-a1*c2)/(a1*b2-a2*b1)
      		//	let D=a1*b2-a2*b1
      		//	==> x=(b1*c2-b2*c1)/D, y=(a2*c1-a1*c2)/D
      		Point point = { 
      			(line1.b * line2.c - line2.b * line1.c) / (float)D, 
      			(line2.a * line1.c - line1.a * line2.c) / (float)D 
      		};
      		points.insert(point);
      		break;
      	}
      }
      
    • 集合的排序和精度的确定

      bool operator == (const Point& other) const {
      	return fabs(x - other.x) < 0.00000001 && fabs(y - other.y) < 0.00000001;
      	}
      bool operator < (const Point& other) const {
      	if (x != other.x) {
      		return x < other.x;
      	}
      	else {
      		return y < other.y;
      	}
      }
      

    附加题

    1. 解题思路

    [已知两圆 (x-x_1)^2+(y-y_1)^2=(r_1)^2 和 (x-x_2)^2+(y-y_2)^2=(r_2)^2 ,\设圆心距为 d,则有 egin{cases}两圆内含或相离, &d<|r_1-r_2| 或 d>r_1+r_2\两圆相交或相切,&|r_1-r_2|leq dleq r_1+r_2end{cases}\egin{cases}(x-x_1)^2+(y-y_1)^2=(r_1)^2\(x-x_2)^2+(y-y_2)^2=(r_2)^2end{cases}implies 2(x_2-x_1)x+2(y_2-y_1)y+x_1^2-x_2^2+y_1^2-y_2^2+r_2^2-r_1^2\implies egin{cases}(x-x_1)^2+(y-y_1)^2=(r_1)^2\ 2(x_2-x_1)x+2(y_2-y_1)y+x_1^2-x_2^2+y_1^2-y_2^2+r_2^2-r_1^2end{cases}implies 求出交点坐标 ]

    2. 代码说明

    • 计算直线与圆交点(思路请见代码注释)
      // calculate the intersections of line and Circle
      static void calLineCircleIst(Line& line, Circle& circle) {
      	int intercept;
      	// intercept=r^2-d^2=r^2-(ax+by+c)^2/(a^2+b^2)
      	intercept = (int)(pow(circle.r, 2) - pow(line.a * circle.x + line.b * circle.y + line.c, 2) / (pow(line.a, 2) + pow(line.b, 2)));
      	// not intersect
      	if (intercept < 0) { return; }
      	// tLine is perpendicular to line
      	Line tLine = { line.b, -line.a, line.a * circle.y - line.b * circle.x };
      	int D;
      	D = tLine.a * line.b - line.a * tLine.b;
      	// tPoint is the intersection of line and tLine
      	Point tPoint = {
      		(tLine.b * line.c - line.b * tLine.c) / (float)D,
      		(line.a * tLine.c - tLine.a* + line.c) / (float)D
      	};
      	switch (intercept) 
      	{
      	case 0:	// line is tangent to circle
      		points.insert(tPoint);
      		break;
      	default:// line passes through circle
      		float vecX;
      		float vecY;
      		float offset;
      		// (vecX, vecY) is a unit vector
      		vecX = (float)(line.b / sqrt(pow(line.a, 2) + pow(line.b, 2)));
      		vecY = (float)(-line.a / sqrt(pow(line.a, 2) + pow(line.b, 2)));	
      		// Offset is half of the intercept
      		offset = (float)sqrt(intercept / (pow(line.a, 2) + pow(line.b, 2)));
      		// intersection = tPoint +/- vec*offset
      		Point ist1 = { tPoint.x + vecX * offset, tPoint.y + vecY * offset };
      		Point ist2 = { tPoint.x - vecX * offset, tPoint.y - vecY * offset };
      		points.insert(ist1);
      		points.insert(ist2);
      		break;
      	}
      }
      
    • 计算圆与圆交点(思路请见代码注释)
      // calculate intersections of two circles
      static void calCircleCircleIst(Circle& circle1, Circle& circle2) {
      	int radiusSum;
      	int radiusDiff;
      	int centerDis;
      	radiusSum = (int)pow(circle1.r + circle2.r, 2);
      	radiusDiff = (int)pow(circle1.r - circle2.r, 2);
      	centerDis = (int)(pow(circle1.x - circle2.x, 2) + pow(circle1.y - circle2.y, 2));
      	// not intersect
      	if (centerDis > radiusSum || centerDis < radiusDiff) {
      		return;
      	}
      	// line passes both two intersections of circles
      	Line line = {
      		circle1.d - circle2.d, 
      		circle1.e - circle2.e,
      		circle1.f - circle2.f
      	};
      	// the intersections of two circles are also the intersections of line and circle
      	calLineCircleIst(line, circle1);
      }
      

    截图 - 测试与度量

    1. 单元测试覆盖率

    img

    2. Code Quality Analysis 警告消除

    img
  • 相关阅读:
    junit源码解析--测试驱动运行阶段
    junit源码解析--初始化阶段
    junit源码解析--核心类
    junit测试套件
    junit参数化测试
    junit忽略测试方法
    Junit4常用注解
    泛型技术
    在使用Java8并行流时的问题分析
    Linux 常用性能分析命令
  • 原文地址:https://www.cnblogs.com/FUJI-Mount/p/12456311.html
Copyright © 2020-2023  润新知