代码改变世界

第一次个人项目

2020-03-10 13:53  gzhBuaa  阅读(178)  评论(2编辑  收藏  举报

教学班级:006,周五上午3、4节班。
项目地址:https://github.com/gzhGit/personalwork1.git
psp表格:

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

解题思路

  • 拿到题目,构思如下:几何对象分两类——直线和圆,读一行几何参数生成一个几何对象。设置一个存储几何对象的vector,将刚生成的几何对象与vector中存储的之前生成的几何对象,输入一个负责计算的类,该类内置三个方法:计算直线与直线的交点、计算直线和圆的交点、计算圆和圆的交点。三个方法均返回内置坐标的交点对象,把交点放入set中,避免重复。当该几何对象与之前生成的所有几何对象一一计算完成后,将其存入vector中。
  • 核心的部分在于计算类的实现。在网上可以找到计算直线交点、直线与圆的交点、圆和圆的交点公式,表示几何对象的参数无非是以下几种:特殊直线标志s,斜率k,截距c,圆心坐标(a,b),半径r。通过以上几个参数,可以得到几何对象的方程形式,并通过联立求解、判断特殊情况(如直线平行)等方式计算出交点坐标,或者返回一个特殊的值,表示不存在交点。

实现过程

  • :两种几何对象分别设置类:Line,内部包含斜率k(double)、截距c(double)、特殊标志s(int);Circle,内部包含圆心坐标(a(int),b(int))、半径r(int)。为了使两种几何对象可以统一表示,设置几何对象类Object,内部包含Line和Circle对象的指针,以及一个标志来表示其内部的两个指针哪个是有效的,以此实现Object类的二重性。设置表示交点的类Node,包含无交点标志judge(int)、x坐标x(double)、y坐标y(double)。设置负责计算的类Intersect,输入几何对象,返回包含1或2个Node对象的vector。若无交点,则置judge为1。
  • 单元测试:由于计算集中在Intersect类,其他类只具有一些内置属性,所以单元测试对Intersect类的三个计算交点的方法进行测试。测试代码实现了对三个计算函数的全部分支覆盖,部分单元测试代码如下:
//直线对象
Line* line1 = new Line(0, 0, 1, 1);
Line* line2 = new Line(2, 0, 2, -1123);
//圆对象
Circle* circle1 = new Circle(2, 2, 1);
Circle* circle2 = new Circle(0, 0, 3);
Circle* circle3 = new Circle(0, 0, 2);
Circle* circle4 = new Circle(10, 0, 1);
//几何对象,可表示直线或圆
Object o1(0, line1);
Object o2(1, circle1);
Object o3(1, circle2);
//主要函数测试
Intersect i;
vector<Node> v1 = i.circleCrossCircle(*(o2.circle), *(o3.circle));
vector<Node> v2 = i.lineCrossCircle(*(o1.line), *(o2.circle));
vector<Node> v3 = i.lineCrossLine(*line1, *line2);
vector<Node> v4 = i.lineCrossCircle(*line2, *circle2);
vector<Node> v5 = i.lineCrossCircle(*line2, *circle3);
vector<Node> v6 = i.lineCrossCircle(*line1, *circle4);
int size1 = v1.size();
int size2 = v2.size();
Assert::AreEqual(size1, 2);
Assert::AreEqual(size2, 2);
Assert::AreEqual((int)v3.size(), 1);
Assert::AreEqual((int)v4.size(), 2);
Assert::AreEqual((int)v5.size(), 1);
Assert::AreEqual((int)v6.at(0).judgeCross, 0);
  • 单元测试的主要对象是计算类Intersect,集中测试了其内部函数lineCrossLine()、lineCrossCircle()、circleCrossCircle(),同时还测试了Object类能否正确表示。
  • 以上代码是单元测试的一部分,仅对求交点的三个函数进行粗略测试。完整的单元测试代码已提交至github,测试情况包括:两直线平行、相交(由于采用斜截式存储直线,另外测试了直线与x轴垂直时,平行与相交的情况),直线(斜率存在以及与x轴垂直的两种情况)与圆相离、相切、相交,圆与圆相离、外切、相交、内切、内含,覆盖三个求交点函数的所有分支。目前代码覆盖率工具在安装过程中出现了一些问题,暂时无法得到覆盖代码的详细统计结果。

性能分析

  • 计算圆和直线、圆和圆交点坐标时,最初是统一按照一般式方程联立求解,即将直线表示为Ax+By+C=0,将圆表示为x2+Dx+y2+Ey+F=0,求出参数值,按照求根公式统一联立求解。觉得这样比较统一简便,不管两个几何对象是否有交点,或者交点有几个,只需要通过求根公带入求解即可。
  • 后来思考了一下,求根公式的计算开销还是比较大的,并不是所有情况下都要通过求根公式来计算。比如通过计算直线和圆心的距离,如果大于半径就直接返回代表无交点的值即可。
  • 使用vs2015自带的性能探查器,分析项目,得到如下运行结果。由于样例规模较小,main函数调用读文件函数开销占比较大。在自定义函数中,可以在图中看出intersect类(负责计算)的lineCrossLine()开销较大,即求直线交点运算过程的耗时占比较大,性能瓶颈在于优化运算过程。

代码说明:

  • 关键代码部分是从前向后遍历存储几何对象的容器,求出交点:
	int count = 0;
	//遍历objVector,求二者交点
	for (count = 0; count < countLines; count++) {
		Object objCross = objVector->at(count);
		vector<Node> result;	//存储几何对象交点
		Intersect cal;
		//当前几何对象与直线求交点
		if (obj.lineOrCircle == 0) {
			//直线与直线相交
			if (objCross.lineOrCircle == 0) {
				result = cal.lineCrossLine(*(obj.line), *(objCross.line));
			}
			//直线与圆相交
			else if (objCross.lineOrCircle == 1) {
				result = cal.lineCrossCircle(*(obj.line), *(objCross.circle));
			}
		}
		//当前几何对象与圆求交点
		else if (obj.lineOrCircle == 1) {
			//圆与直线相交
			if (objCross.lineOrCircle == 0) {
				result = cal.lineCrossCircle(*(objCross.line), *(obj.circle));
			}
			//圆与圆相交
			else if (objCross.lineOrCircle == 1) {
				result = cal.circleCrossCircle(*(obj.circle), *(objCross.circle));
			}
		}
		
		//将求解所得的交点存入nodeSet
		int runover;
		for (runover = 0; runover < (int)result.size(); runover++) {
			Node node = result.at(runover);
			if (node.judgeCross == 1) {
				nodeSet->insert(node);
			}
		}
	}
  • objVector是存储几何对象的容器,每读入一行几何参数,就生成一个几何对象,记作obj。从前向后遍历objVector,将obj与容器中的每个几何对象求解交点,其中有细分的四种情况:直线和直线求交点,直线和圆求交点,圆和直线求交点(与前者调用同一函数),圆和圆求交点。求解交点的函数返回一个存储交点的容器,检查这些交点,如果其judgeCross值为0,说明二者无交点;反之说明二者存在交点,将交点存入nodeSet中。

  • 使用vs2017的运行代码分析,对项目进行检测,结果无警告。截图如下: