- [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号验证失败");
        }
    }
}
(1) 不熟练正则表达式的使用,开始的正则表达式匹配不完整,[1-9][\\d] 仅匹配前两位(首字符非0,第二位为数字),但未校验后续字符;若输入12aaaaa会被错判为合法。

(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)改进建议
  1. 电梯多类设计仍然存在不合理不规范的情况:代码集中在Controlle类
    (包含addIntenalRequest,addExternalRequest,processRequests,getMaxUpFloor,getMinDownFloor,handleRequestsAtCurrentFloor等方法)中;
    其中的addIntenalRequest和addExternalRequest方法应该放在RequestsQueue类中才合理,后续应该对相应代码改进与优化。

  2. 电梯类算法设计冗长,有一些逻辑可以合并一起处理;

例如如下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)总结
  1. 通过3周的Java题目集训练,我基本上能理解和实现简单的Java面向对象编程,理解了类与对象的封装特性,初步掌握了基本的模块化设计的实现方法。
    例如,通过电梯调度系统的开发,实践了将复杂功能拆解为独立对象(如电梯本体、请求队列、调度器)的工程化思维,初步了解了代码结构化设计模式。

  2. 在面对复杂的算法逻辑题时,没有认真分析题目,找到最佳的算法思路,导致浪费很多时间,效率低
    例如:在写题目集5电梯类时,一开始就直接分析三个队列的不同组合情况(内部需求,外部向上需求,外部向下需求),不但草稿演算浪费5,6张纸,最后运行调试后发现电梯算法(与后续新增的测试样例)部分电梯运行过程是错的;最后发现分析两个队列(内部需求,外部需求)仅可,而(向上和向下)只需要判断一下方向即可。

  3. 目前的有关类设计的题目,课程组老师均给出了类图,对应着类图编程相对比较轻松。只需要思考每个类里的方法算法逻辑如何实现的即可。如果不看对应的类图直接写代码,我目前对面向对象编程的思路会不清晰没有逻辑,又会回到面向过程的编程中去;因此,在后续的学习中,需要进一步培养分析对象和类设计的能力,更好的适应面向对象编程。