- [1] 前言
- [2] 设计与分析
- [3] 采坑心得
- [4] 改进建议
- [5] 总结
- (1)前言
本篇文章总体分析PTA中题目集5-题目集7:
<1> 题目集5总共5道题目,前四道均为简单题,主要考察对正则表达式基础的应用,而第五道为算法复杂的电梯调度程序;
<2> 题目集6总共3道题,前两道为算法简单的面向对象类设计题,第三道为题目集5电梯调度程序的重构和模块化设计,必须符合单一职责原则(SRP);
<3> 题目集7总共3道题,前两道为算法简单的类设计题,第三道为题目集6电梯调度程序的二次迭代。
- (2)设计与分析
1.题目集5电梯单部调度问题:
SouceMonitor分析图:
解释:
根据左边的Kiviat图和上面准确的Value值可知:每个方法的平均语句数Avg Stmts/Method(23.56)远超绿色范围的正常值,说明存在代码堆积,过度嵌套多重if-else语句。实际上确实如此,第一次的电梯算法中还停留在面向过程的解题思维中,源码中总共也就两个类(elevator和Main),许多方法全部堆砌在elevator类中,缺乏模块化思维。
心得:
合理分析不同算法逻辑的实现过程,尽量把类里的每个方法各自独立实现自己的特有功能,把不同的功能放在不同的方法中代码可读性也强,对代码改进和优化时也轻松。
2. 题目集6电梯单部调度问题:
类图:
SouceMonitor分析图:
解释:
根据左边的Kiviat图和上面准确的Value值可知:对比上次的题目集5的电梯算法,这次的每个方法的平均语句数Avg Stmts/Method(8.35)处于绿色范围的正常值,说明不存在代码过度堆积问题。但最大圈复杂度Max Complexity(11)超过正常值,存在过度嵌套多重if-else语句。分析源代码中,确实有两个方法getMaxFloor()和getMinFloor(),在分析电梯上行和下行过程中,使用多个if-else语句判断楼层和方向。此外我的最终源代码500行远超过PTA的代码限度,无法正常提交代码,说明存在许多冗余和无效的代码量,或者类的设计不规范,不高效,后续需要进一步改进与优化。
心得:
面对过于复杂的算法和逻辑,不宜过多使用if-else语句,后续需要进一步学习,用面向对象的思维重构代码。
3. 题目集7电梯单部调度问题:
SouceMonitor分析图:
解释
题目集7目前的电梯程序只实现了部分测试数据和算法逻辑,对于当前的源码进行分析可知:
最大圈复杂度Max Complexity远超过正常值,后续仍需要对源码进行合理优化与重构。
- (3)采坑心得
1.前部分题目心得
提交的错误源码:
点击查看代码
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
if(input.length() <= 4 || input.length() >= 16) {
System.out.print("你输入的QQ号验证失败");
return;
}
if(input.matches("[1-9][\\d]")) {
System.out.print("你输入的QQ号验证成功");
}else {
System.out.print("你输入的QQ号验证失败");
}
}
}
(2)不熟练正则表达式的使用,长度校验和数字校验均可隐藏在正则中,
首字符非0 ✔[1-9]
全数字校验和长度校验 ✔\d{4,1}
(3)随机计数的点分布在内接圆中为整形(int hits),返回ruturn时准确值不够精确,丢失了部分小数点后的精确值,导致与π的差值较大。因此需要强制转换会double类型。
强制转换类型的代码:
点击查看代码
public double simulation(int n) {
int hits = 0;
for(int i = 0;i < n;i++) {
double x = rectangle.coordinate.getAbscissa() + Math.random() * rectangle.getLength();
double y = rectangle.coordinate.getOrdinate() - Math.random() * rectangle.getWidth();
Coordinate randomcoordinate = new Coordinate(x,y);
if(getDistance(circle.getCoordinate(),randomcoordinate) <= circle.getRadius()) {
hits++;
}
}
return (double)(4 * hits) / n;//强制int转换为double
}
2. 单部电梯调度问题:
提交的问题:
采坑心得体会:
在不断改进和提交源码的过程中,前部分源码提交过程大部分出现的问题为运行超时,说明算法实现的逻辑不完整,有漏洞导致死循环,需要改正和调整正确逻辑和算法;
后部分源码提交过程出现的问题几乎全是答案错误,说明逻辑实现了闭环,但源码算法实现的过程与测试点实现的需求不符合,需要更改错误实现的逻辑和算法。
- (4)改进建议
-
电梯多类设计仍然存在不合理和不规范的情况:代码集中在Controlle类
(包含addIntenalRequest,addExternalRequest,processRequests,getMaxUpFloor,getMinDownFloor,handleRequestsAtCurrentFloor等方法)中;
其中的addIntenalRequest和addExternalRequest方法应该放在RequestsQueue类中才合理,后续应该对相应代码改进与优化。 -
电梯类算法设计冗长,有一些逻辑可以合并一起处理;
例如如下getMaxUpFloor()方法:
点击查看代码
private TargetFloor getMaxUpFloor() {
// 定义候选变量
Integer insideCandidate = null;
Integer outsideCandidate = null;
int currentFloor = elevator.getCurrentFloor();
LinkedList<Integer> outsideUpRequests = queue.getExternalUpRequests();
LinkedList<Integer> outsideDownRequests = queue.getExternalDownRequests();
if (!queue.getInternalRequests().isEmpty() && queue.getInternalRequests().peek() >= currentFloor) {
insideCandidate = queue.getInternalRequests().peek();
}
if (!queue.getExternalRequests().isEmpty() && queue.getExternalRequests().peek() >= currentFloor) {
outsideCandidate = queue.getExternalRequests().peek();
}
if (queue.getInternalRequests().isEmpty()) {
if (queue.getExternalRequests().isEmpty()) {
return null;
}
else if(!queue.getExternalRequests().isEmpty()) {
if(queue.getExternalRequests().peek() == outsideDownRequests.peek()) {
return new TargetFloor(queue.getExternalRequests().peek(), "DOWN");
}
else if(queue.getExternalRequests().peek() == outsideUpRequests.peek()) {
return new TargetFloor(queue.getExternalRequests().peek(), "UP");
}
}
}
else if(!queue.getInternalRequests().isEmpty() && insideCandidate == null) {//内部无效
if(queue.getExternalRequests().isEmpty()) {
return new TargetFloor(queue.getInternalRequests().peek(), "DOWN");
}
else if(outsideCandidate != null) {
if(queue.getExternalRequests().peek() == outsideUpRequests.peek()) {
return new TargetFloor(queue.getExternalRequests().peek(), "UP");
}
else if(queue.getExternalRequests().peek() == outsideDownRequests.peek() && queue.getExternalRequests().peek() < outsideDownRequests.peek()) {
return new TargetFloor(queue.getExternalRequests().peek(), "DOWN");
}
else if(queue.getExternalRequests().peek() == outsideDownRequests.peek() && queue.getExternalRequests().peek() > outsideDownRequests.peek()) {
return new TargetFloor(queue.getExternalRequests().peek(), "UP");
}
}
else if(outsideCandidate == null) {//外部无效
if(queue.getInternalRequests().peek() > queue.getExternalRequests().peek()) {
return new TargetFloor(queue.getInternalRequests().peek(), "DOWN");
}
else if(queue.getInternalRequests().peek() == queue.getExternalRequests().peek()) {
if(queue.getExternalRequests().peek() == outsideDownRequests.peek()) {
return new TargetFloor(queue.getExternalRequests().peek(), "DOWN");
}
else if(queue.getExternalRequests().peek() == outsideUpRequests.peek()) {
return new TargetFloor(queue.getExternalRequests().peek(), "DOWN");
}
}
else if(queue.getInternalRequests().peek() < queue.getExternalRequests().peek()) {
if(queue.getExternalRequests().peek() == outsideUpRequests.peek()) {
return new TargetFloor(queue.getInternalRequests().peek(), "DOWN");
}
else if(queue.getExternalRequests().peek() == outsideDownRequests.peek()) {
return new TargetFloor(queue.getExternalRequests().peek(), "DOWN");
}
}
}
}
else if(insideCandidate != null) {
if(queue.getExternalRequests().peek() == null) {
return new TargetFloor(queue.getInternalRequests().peek(), "UP");
}
else if(outsideCandidate == null) {//外部无效
return new TargetFloor(queue.getInternalRequests().peek(), "UP");
}
else if(queue.getInternalRequests().peek() < queue.getExternalRequests().peek()) {
return new TargetFloor(queue.getInternalRequests().peek(), "UP");
}
else if(queue.getInternalRequests().peek() == queue.getExternalRequests().peek()) {
if(queue.getExternalRequests().peek() == outsideUpRequests.peek()) {
return new TargetFloor(queue.getExternalRequests().peek(), "UP");
}
else if(queue.getExternalRequests().peek() == outsideDownRequests.peek()) {
return new TargetFloor(queue.getExternalRequests().peek(), "UP");
}
}
else if(queue.getInternalRequests().peek() > queue.getExternalRequests().peek()) {
if(queue.getExternalRequests().peek() == outsideUpRequests.peek()) {
return new TargetFloor(queue.getExternalRequests().peek(), "UP");
}
else if(queue.getExternalRequests().peek() == outsideDownRequests.peek()) {
return new TargetFloor(queue.getInternalRequests().peek(), "UP");
}
}
}
return null;
}
其它的像getMinDownFloor()方法与上部分方法类似,均可以优化处理不必要和无效的代码量。
- (5)总结
-
通过3周的Java题目集训练,我基本上能理解和实现简单的Java面向对象编程,理解了类与对象的封装特性,初步掌握了基本的模块化设计的实现方法。
例如,通过电梯调度系统的开发,实践了将复杂功能拆解为独立对象(如电梯本体、请求队列、调度器)的工程化思维,初步了解了代码结构化设计模式。 -
在面对复杂的算法逻辑题时,没有认真分析题目,找到最佳的算法思路,导致浪费很多时间,效率低。
例如:在写题目集5电梯类时,一开始就直接分析三个队列的不同组合情况(内部需求,外部向上需求,外部向下需求),不但草稿演算浪费5,6张纸,最后运行调试后发现电梯算法(与后续新增的测试样例)部分电梯运行过程是错的;最后发现分析两个队列(内部需求,外部需求)仅可,而(向上和向下)只需要判断一下方向即可。 -
目前的有关类设计的题目,课程组老师均给出了类图,对应着类图编程相对比较轻松。只需要思考每个类里的方法算法逻辑如何实现的即可。如果不看对应的类图直接写代码,我目前对面向对象编程的思路会不清晰,没有逻辑,又会回到面向过程的编程中去;因此,在后续的学习中,需要进一步培养分析对象和类设计的能力,更好的适应面向对象编程。