电梯题目blog

1.前言:首先第一次题目集我没有在规定时间内做出来,但后来又花了一些时间,最后还是解决了这个问题,我觉得第一次其实是最难的,因为最开始不太理解这个电梯运行的规则;第二次是要将第一次的代码从只有主类和电梯类分为电梯类、乘客请求类、队列类以及控制类,但其实我第一次的代码虽然通过了测试点,但还是有很多逻辑上的漏洞,所以在控制类分出来后还是做了很大的改动;第三次题目发生了一些改变,外部输入从<源楼层,方向>变成了<源楼层,到达楼层>,但结果前两次迭代,已经基本了解了电梯的运行规则,所以写起来会比前两次快很多。现在我理解的电梯调度规则是:优先解决接下来运行方向和当前电梯运行相同且请求楼层在当前电梯运行方向上的请求,每次分别读取外部请求队列和内部请求队列的第一个元素,按照上述规则选择最适合的去执行,如果执行的是外部请求,电梯方向更改为外部请求方向。

2.设计与分析:
第一次:Maximum Complexity为43,整体方法复杂度较高,主要是因为Elevator类中的control方法嵌套了很多else if语句,最大嵌套深度达6层;方法复杂度最大达8,不满足职责单一原则;但表中方法均为低复杂度,雷达图却显示存在高复杂度方法,说明是少数高复杂度的方法拉高了平均值。

Metrics Summary For Checkpoint 'Baseline'


Parameter Value
========= =====
Project Directory D:\blog代码分析
Project Name elevator1
Checkpoint Name Baseline
Created On 19 Apr 2025, 10:26:06
Files 1
Lines 214*
Statements 160
Percent Branch Statements 26.9
Method Call Statements 69
Percent Lines with Comments 0.0
Classes and Interfaces 2
Methods per Class 10.50
Average Statements per Method 6.19
Line Number of Most Complex Method {undefined}
Name of Most Complex Method Elevator.control()
Maximum Complexity 43*
Line Number of Deepest Block {undefined}
Maximum Block Depth 5
Average Block Depth 2.67
Average Complexity 4.21*


Most Complex Methods in 2 Class(es): Complexity, Statements, Max Depth, Calls

Elevator.control() 43, 58, 5, 35
Main.main() 7
, 20, 5, 12


Block Depth Statements

0 4
1 26
2 45
3 36
4 42
5 7
6 0
7 0
8 0
9+ 0


Metrics Summary For Checkpoint 'Baseline'

第二次:最大圈复杂度达到了30,超过阈值3倍,需要做拆分;最大嵌套深度达到了6,是由于多重if else语句造成的;Agent类中的isLastExternal方法复杂度高达30,并且调用次数多达18次,control方法复杂度达16,深度达6,调用次数达15次,判断逻辑耦合性过强


Parameter Value
========= =====
Project Directory D:\blog代码分析
Project Name elevator2
Checkpoint Name Baseline
Created On 19 Apr 2025, 10:49:24
Files 1
Lines 382*
Statements 291
Percent Branch Statements 18.2
Method Call Statements 110
Percent Lines with Comments 0.0
Classes and Interfaces 5
Methods per Class 7.00
Average Statements per Method 6.77
Line Number of Most Complex Method {undefined}
Name of Most Complex Method Agent.isLastExternal()
Maximum Complexity 30*
Line Number of Deepest Block {undefined}
Maximum Block Depth 6
Average Block Depth 2.88
Average Complexity 3.41*


Most Complex Methods in 7 Class(es): Complexity, Statements, Max Depth, Calls

addInternal().lastInternalFloor.equals() 1, 2, 3, 1
Agent.isLastExternal() 30
, 38, 5, 18
Elevator.moveManagement() 4, 10, 4, 3
Main.main() 9
, 29, 5, 13
Queue.externalManagement() 1, 6, 2, 2
removeSame().thenDirection.equals() 3
, 5, 4, 2
Request.addInternal() 1*, 2, 3, 1


Block Depth Statements

0 7
1 54
2 80
3 53
4 32
5 53
6 12
7 0
8 0
9+ 0




第二次我分了类后的代码并没有完全按照给的类图来设计,我没有通过枚举类来完成电梯方向和开关门状态的实现,而是直接通过字符串实现的

第三次:Controller类的control方法复杂度高达28,接近阈值的3倍,保持职责单一原则,提高可复用性,就要降低每个方法的复杂度,这个方法的深度达到了6,有大量的if else语句,并且这个方法被调用了11次,一旦出错影响广泛;主方法的复杂度也偏高,应该更加模块化。


Parameter Value
========= =====
Project Directory D:\blog代码分析
Project Name elevator3
Checkpoint Name Baseline
Created On 19 Apr 2025, 11:31:32
Files 1
Lines 273*
Statements 194
Percent Branch Statements 16.0
Method Call Statements 61
Percent Lines with Comments 0.0
Classes and Interfaces 6
Methods per Class 6.50
Average Statements per Method 3.36
Line Number of Most Complex Method {undefined}
Name of Most Complex Method Controller.control()
Maximum Complexity 28*
Line Number of Deepest Block {undefined}
Maximum Block Depth 6
Average Block Depth 2.22
Average Complexity 2.25*


Most Complex Methods in 5 Class(es): Complexity, Statements, Max Depth, Calls

Controller.control() 28, 12, 5, 11
Elevator.isValidFloor() 4
, 4, 2, 0
Main.main() 7, 20, 4, 11
Passenger.getDirection() 1
, 1, 2, 0
RequestQueue.addExternalRequest() 3*, 5, 2, 3


Block Depth Statements

0 8
1 55
2 73
3 19
4 24
5 13
6 2
7 0
8 0
9+ 0


3.踩坑心得
第一次:
(1)主方法中循环调用控制方法的循环条件有问题,因为外部和内部队列处理完一个请求就会移除该请求,而i一直在增加,导致一直无法输出全部运行结果
修改前:

for(int i=0;i<(elevator.getInternalRequestSize()+elevator.getExternalRequestSize());i++){
    elevator.control();
    }

当输入:

1
20
<3,UP>
<5>
<6,DOWN>
<7>
<3>
end

输出:

Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Open Door # Floor 7
Close Door

无法正常运行所以请求,因为主方法中循环条件有问题

进程已结束,退出代码为 0

修改后:

while(elevator.getInternalRequestSize()>0||elevator.getExternalRequestSize()>0){
     elevator.control();
    }

(2)开始时我先将数据存入数组,再检查它是否为end,导致end进入数组无法被正确解析,造成非零返回的结果,修改后先检查如果不是end再加入队列

 if(input.equalsIgnoreCase("end"))
   break;
 else
   list.add(input);

(3)控制电梯移动的方法最开始写的很复杂,将向上走和向下走分为两个循环完成,但其实后面发现这样将逻辑复杂化了,只要请求楼层和到达楼层不相等电梯就要移动,所以循环条件可以变成一个;并且运行时每
一层都要有输出,到达目的楼层后也有不同的输出,所以可以写成两个不同的方法,每次调用时楼层参数用当前修改后的即可,修改后:

public void moveManagement(int tofloor){
  while(floor!=tofloor){
    if(floor>tofloor){
      floor--;
      direction="DOWN";
      printmove();
     }
     else{
      floor++;
      direction="UP";
      printmove();
     }
  }
  printopen();
}

(4)修改前的代码:

    while(true){
        input=scanner.nextLine();
        if(input.equalsIgnoreCase("end"))
            break;
        list.add(input);
    }
    if(list.contains(",")){
        String[] part=input.split(",");
        int tofloor=Integer.parseInt(part[0]);
        String direction=part[1];
        elevator.exterQueueManagement(tofloor,direction);
    }
    else{
        int tofloor=Integer.parseInt(input);
        elevator.innerQueueManagement(tofloor);
    }

这样会导致出循环时input中存储的时end这个字符串,无法完成转化为整型数这一步
第二次:
(1)为简化代码,先判断楼层是否合理,合理再加入队列中

  public void addExternal(String input){
    String[] part=input.split(",");
    int externalFloor=Integer.parseInt(part[0]);
    externalFloorList.add(externalFloor);
    if((!lastExternalInput.equals(input))&&(externalFloor>=elevator.minFloor)&&(externalFloor<=elevator.maxFloor))
        externalRequest.add(input);
    lastExternalInput=input;
}
public void addInternal(int tofloor){
    if((!lastInternalFloor.equals(tofloor))&&(tofloor>=elevator.minFloor)&&(tofloor<=elevator.maxFloor)){
        internalRequest.add(tofloor);
        lastInternalFloor=tofloor;
    }
}

可以减少后续控制类中运行电梯时需要考虑的点
(2)对第一次的代码拆分后,运行后发现不能正常处理上一次执行外部队列请求后方向根据请求方法改变,所以在主方法中增加

if((!request.externalRequest.isEmpty())&&(queue.lastMethod.equals("externalManagement"))){
            boolean is=agent.isLastExternal();
            if(!is)
                agent.control();
        }
        else
            agent.control();

如果上一次执行的是外部队列请求,单独执行新增的方法public boolean isLastExternal(),方向使用上一次执行的外部请求自带的方向
(3)转变方向方法的补全,增加外部队列为空和内部队列为空时转变方向的方法

public String changeExternalDirection(String lastDirection,int externalFloor)
{
    if(lastDirection.equals("UP")){
        if(externalFloor<elevator.currentFloor)
            return "DOWN";
    }
    if(lastDirection.equals("DOWN")){
        if(externalFloor>elevator.currentFloor)
            return "UP";
    }
    return lastDirection;
}
public String changeInternalDirection(String lastDirection,int internalFloor){
    if(lastDirection.equals("UP")){
        if(internalFloor<elevator.currentFloor)
            return "DOWN";
    }
    if(lastDirection.equals("DOWN")){
        if(internalFloor>elevator.currentFloor)
            return "UP";
    }
    return lastDirection;
}

可以避免因为某个队列为空而无法正常进行条件判断的情况
(4)最开始理解为队列里有该楼层,后续遇见相同楼层就不加入队列,但

  1
  10
  <3>
  <3,UP>
  <3,DOWN>
  end

这个样例输出错误,

  Current Floor: 1 Direction: UP
  Current Floor: 2 Direction: UP
  Current Floor: 3 Direction: UP
  Open Door # Floor 3
  Close Door
  Open Door # Floor 3
  Close Door         

从这个样例的输出中可以看出有三个请求,但最后只执行了两个,因为执行完第一个内部请求,电梯方向为向上,因为外部请求队列的第一个楼层,方向都与当前相同,所以被移除,但第二个外部请求虽然楼层与当前相同但方向与当前相反,所以保留这个请求,所以删除方法修改为

 public void removeSame(){
    thenDirection=changeBothDirection(lastDirection,internalFloor,externalFloor);
    if((internalFloor==externalFloor)&&(thenDirection.equals(thDirection))){
        if(request.internalRequest.size()>1){
            request.internalRequest.remove(0);
            internalFloor=request.internalRequest.get(0);
        }
        else
            request.internalRequest.clear();
    }
}

第三次:
(1)第一个示例外部队列的一个元素的源楼层和内部队列的第一个元素相同,但因为外部队列的一个元素的源楼层大于目的楼层,但电梯到达5楼时方向为向上,所以会先执行内部请求

1
20
<5,4>
<5>
<7>
end

所以这种情况的实现为

            else if(sourceFloor==toFloor){
            if((sourceFloor<destinationFloor)&&(thenDirection==Direction.UP)){
                externalManagement(sourceFloor);
            }
            else if((sourceFloor<destinationFloor)&&(thenDirection==Direction.DOWN)){
                internalManagement();
            }
            else if((sourceFloor>destinationFloor)&&(thenDirection==Direction.UP)){
                internalManagement();
            }
            else if((sourceFloor>destinationFloor)&&(thenDirection==Direction.DOWN)){
                externalManagement(sourceFloor);
            }
        }

还是需要考虑当前电梯方向和接下来的运行方向
(2)在获取数据时,要分为外部和内部两种情况并且检查当前队列是否为空,否则可能会出现某个变量赋值出错的情况

    if(!requestQueue.internalRequest.isEmpty())
        toFloor=requestQueue.internalRequest.get(0);
    if(!requestQueue.externalRequest.isEmpty()){
        String first=requestQueue.externalRequest.get(0);
        String[] part=first.split(",");
        sourceFloor=Integer.parseInt(part[0]);
        destinationFloor=Integer.parseInt(part[1]);

(3)当执行外部请求,到达源楼层后,会自动将目的楼层移到内部请求队列中,所以应该是在执行了外部请求后再完成这一个过程,但是在移除第一个元素之前,不然添加的元素将和预期值不符合
public void externalManagement(int sourceFloor){
requestQueue.internalRequest.add(destinationFloor);
move(sourceFloor);
requestQueue.externalRequest.remove(0);
}
修改前的输出是

Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Open Door # Floor 7
Close Door
Current Floor: 6 Direction: DOWN
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Open Door # Floor 4
Close Door
Open Door # Floor 4
Close Door      

如果是在控制方法中完成这一步而不是队列管理中会导致外部请求的目标楼层在被移除之前都次被加入内部队列,导致最后多次执行该命令。
4.改进建议:这三次的代码都用了大量if else语句,导致代码总长度很长;大量相同的代码写为一个单独的方法,每次使用时调用方法即可。
5.总结:学会了许多处理字符串的方法,比如:input=input.replaceAll("[<>]",""),用于去除输入时两端的括号;input.contains(","),用于检验输入中是否包含逗号,来判断是应该加入外部队列还是内部队列;int tofloor=Integer.parseInt(input),将字符串型转化为整型;input=scanner.nextLine(),用于一行一行的读取,消耗换行符;input.equalsIgnoreCase("end"),不检查大小写,即大小写的end都满足;thenDirection.equals("UP"),需要考虑大小写,只有和引号中内容完全一样才满足;String[] part=request.split(","),将字符串逗号前后分为两个部分;关于ArrayList数组的用法,比如:private ArrayList internalRequest=new ArrayList<>(),定义时写明这个数组中存储的数据类型;externalRequest.add(input),将数据添加入数组;int tofloor=(Integer)internalRequest.get(0),获得该数组某个索引的元素的值;internalRequest.remove(0),移除数组中某个索引的元素;return internalRequest.size(),获取当前数组长度;internalRequest.isEmpty(),检验当前数组是否为空;request.internalRequest.clear(),清空该数组;初始化了的类的实例可作为参数传入其他类,使这个类可以调用这些类中的方法:Agent agent=new Agent(elevator,request,queue);如果要调用其他类中的变量,前提是这个变量是protect或者public类型,不能是private类型;每一个方法做到单一职责,不能太长,方便复用;枚举的用法,先列举出这个变量的所有赋值情况,使用时用.去引用即可。需要进一步学习和改进的地方:如何减少else if的使用,用更简单的方法去替代;类与类之间的关系设计不够清晰,参数传递有些混乱,导致到后面再调用类中的方法是出现未赋值的量,为空;没有在最开始就构思整个框架,导致后面要改很多地方,并且这样会导致代码重复量很多,并且逻辑混乱;建议:线上网课可以更详细一点。

posted @ 2025-04-20 14:22  吴柯潼  阅读(21)  评论(0)    收藏  举报