无聊之作:简单模拟电梯程序[Java]


记得刚开始学Java时用的是《Java大学教程》,这本书每章都在介绍一点电梯程序的样子,加上上班天天都要坐电梯,突发奇想想要做一个模拟电梯程序。

一开始没想清楚电梯程序的运行方式,但是知道几个关键表现。

  • 电梯有静止的时候
  • 电梯往往停在最后请求的楼层,也有最后静止在底层(1楼)的,这相当于电梯在没有请求时自己按了一下1
  • 电梯存在插队现象,即电梯在1楼,A在8楼先按,B在4楼后按,如果电梯没到4楼的话,后按的人先进

其实还有一个关键点,一般电梯都是有向上向下提示的,电梯上行时,A在8楼按下,B在4楼按下的话,B是不会插队的。不过如果把这个也考虑在内的话问题会复杂很多,因为操作电梯会有两步:指示方向和指出具体楼层。为了简化问题,我实际考虑每个楼层只有一个按钮,不指示方向,电梯内仍旧有具体到达楼层的按钮。这样的电梯可能有点怪,但也可以用。

电梯问题的重点个人认为在两块地方,一块是接受指令时,第二块是每层楼运行时。
能够想见,接受指令时决定了目标楼层(nextFloor),最简单的情况,电梯一开始就是静止的,那么第一次指令就决定了目标楼层。插队现象也不难理解,如果电梯的当前楼层和目标楼层中间出现了请求,那么电梯的目标楼层就变成了中间这层的请求。考虑到电机运行速度,可能要计算当前楼层加一或者加二以上的范围,否则就可能有安全问题。
每层楼运行时,如果没有新请求,那么电梯会往一个方向(direction)运行,比如上行(up),下行(down),直到抵达目标楼层。抵达之后的操作就是根据现在的方向寻找下一个目标楼层。比如现在上行,到达4层,现在6层有新请求,3层也有请求。那么电梯会往6层去,直到抵达最高层或者上层再没有新请求,再往下走。下行的话正好相反,先往下找请求,直到底层或者没有再往上找。
一句话来说,电梯的核心算法就是如何动态确定下一个目标楼层。

这个算法刚才其实已经差不多说得差不多了。然后是具体需要的参数,个人认为当前楼层(currentFloor),目标楼层(nextFloor),请求楼层(requests,一个BitSet,位集合),方向(direction),状态(status)就差不多了。以下是模拟代码:

import java.util.BitSet;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Elevator implements Runnable {

  enum Direction {
    UP, DOWN;
  }

  // three directions, none means stop
  private Direction direction = null;

  enum Status {
    WAITING, OPEN, CLOSE, RUNNING;
  }

  private Status status = Status.WAITING;
  private static final int MIN_FLOOR = 1;
  private static final int NO_SUCH_FLOOR = -1;
  private int currentFloor = 1;
  // next request floor may be no floor when stop
  private int nextFloor = NO_SUCH_FLOOR;
  private final BitSet requests;

  /**
   * Create with floor count. 
   * Floor starts from one. e.g 10 floors means 1..10.
   * 
   * @param floors
   */
  public Elevator(int floors) {
    this.requests = new BitSet(floors);
  }

  /**
   * Receive floor request. 
   * Set next floor directly when stop, or try to insert next floor
   * if current floor + 1 < request floor < original next floor.
   * 
   * @param floorNumber floor number
   */
  public void receiveRequest(int floorNumber) {
    requests.set(floorNumber);
    System.out.println(String.format("get floor request [%d]", floorNumber));
    if (nextFloor == NO_SUCH_FLOOR) {
      setNextFloor(floorNumber);
    } else if ((currentFloor + 1 <= floorNumber) && floorNumber < nextFloor) {
      nextFloor = floorNumber;
    }
  }

  private void setNextFloor(int floorNumber) {
    if (floorNumber == NO_SUCH_FLOOR) {
      direction = null;
      nextFloor = NO_SUCH_FLOOR;
      status = Status.WAITING;
    } else {
      direction = currentFloor < floorNumber ? Direction.UP : Direction.DOWN;
      nextFloor = floorNumber;
      status = Status.RUNNING;
    }
  }

  private void openAndCloseDoor() {
    status = Status.OPEN;
    System.out.println("open door");
    status = Status.CLOSE;
    System.out.println("close door");
  }

  /**
   * @see java.lang.Runnable#run()
   */
  public void run() {
    // return if not running, may be opening and closing door
    if (status != Status.RUNNING) return;
    // test if reach requested floor
    // if reached, open and close door, find next request floor
    testIfReachRequestFloor();
    // if no next floor found, don't move
    if (nextFloor == NO_SUCH_FLOOR) return;
    // or simulate moving
    currentFloor += (direction == Direction.UP ? 1 : -1);
    // print status itself
    System.out.println(this);
  }

  private void testIfReachRequestFloor() {
    if (currentFloor != nextFloor) return;
    System.out.println("reach floor " + currentFloor);
    openAndCloseDoor();
    requests.clear(currentFloor);
    setNextFloor(findNextRequestFloor());
  }

  private int findNextRequestFloor() {
    // when move upward, find upward then downward
    if (direction == Direction.UP) {
      int nextFloorUpward = findRequestFloorUpward(currentFloor + 1);
      if (nextFloorUpward != NO_SUCH_FLOOR) return nextFloorUpward;
      return findRequestFloorDownward(currentFloor - 1);
    }
    // must be down
    // find downward then upward
    int nextFloorDownward = findRequestFloorDownward(currentFloor - 1);
    if (nextFloorDownward != NO_SUCH_FLOOR) return nextFloorDownward;
    return findRequestFloorUpward(currentFloor + 1);
  }

  private int findRequestFloorUpward(int start) {
    for (int i = start; i < requests.size(); i++) {
      if (requests.get(i)) return i;
    }
    return NO_SUCH_FLOOR;
  }

  private int findRequestFloorDownward(int start) {
    for (int i = start; i >= MIN_FLOOR; i--) {
      if (requests.get(i)) return i;
    }
    return NO_SUCH_FLOOR;
  }

  @Override
  public String toString() {
    return String
        .format(
            "Elevator [direction=%s, status=%s, currentFloor=%s, nextFloor=%s, requests=%s]",
            direction, status, currentFloor, nextFloor, requests);
  }

  public static void main(String[] args) throws InterruptedException {
    int floors = 10;
    Elevator elevator = new Elevator(floors); // 1..10
    ScheduledExecutorService executorService =
        Executors.newScheduledThreadPool(1);
    executorService.scheduleAtFixedRate(elevator, 0, 1, TimeUnit.SECONDS);
    Random random = new Random();
    for (int i = 0; i < floors; i++) {
      elevator.receiveRequest(random.nextInt(floors - 1) + 1);
      Thread.sleep(1000L);
    }
  }

}

实际执行结果如下:

get floor request [5]
Elevator [direction=UP, status=RUNNING, currentFloor=2, nextFloor=5, requests={5}]
Elevator [direction=UP, status=RUNNING, currentFloor=3, nextFloor=5, requests={5}]
get floor request [6]
Elevator [direction=UP, status=RUNNING, currentFloor=4, nextFloor=5, requests={5, 6}]
get floor request [5]
Elevator [direction=UP, status=RUNNING, currentFloor=5, nextFloor=5, requests={5, 6}]
get floor request [1]
reach floor 5
open door
close door
Elevator [direction=UP, status=RUNNING, currentFloor=6, nextFloor=6, requests={1, 6}]
get floor request [7]
reach floor 6
open door
close door
Elevator [direction=UP, status=RUNNING, currentFloor=7, nextFloor=7, requests={1, 7}]
get floor request [9]
reach floor 7
open door
close door
Elevator [direction=UP, status=RUNNING, currentFloor=8, nextFloor=9, requests={1, 9}]
get floor request [2]
Elevator [direction=UP, status=RUNNING, currentFloor=9, nextFloor=9, requests={1, 2, 9}]
get floor request [9]
reach floor 9
open door
close door
Elevator [direction=DOWN, status=RUNNING, currentFloor=8, nextFloor=2, requests={1, 2}]
get floor request [9]
Elevator [direction=DOWN, status=RUNNING, currentFloor=7, nextFloor=2, requests={1, 2, 9}]
get floor request [6]
Elevator [direction=DOWN, status=RUNNING, currentFloor=6, nextFloor=2, requests={1, 2, 6, 9}]
Elevator [direction=DOWN, status=RUNNING, currentFloor=5, nextFloor=2, requests={1, 2, 6, 9}]
Elevator [direction=DOWN, status=RUNNING, currentFloor=4, nextFloor=2, requests={1, 2, 6, 9}]
Elevator [direction=DOWN, status=RUNNING, currentFloor=3, nextFloor=2, requests={1, 2, 6, 9}]
Elevator [direction=DOWN, status=RUNNING, currentFloor=2, nextFloor=2, requests={1, 2, 6, 9}]
reach floor 2
open door
close door
Elevator [direction=DOWN, status=RUNNING, currentFloor=1, nextFloor=1, requests={1, 6, 9}]
reach floor 1
open door
close door
Elevator [direction=UP, status=RUNNING, currentFloor=2, nextFloor=6, requests={6, 9}]
Elevator [direction=UP, status=RUNNING, currentFloor=3, nextFloor=6, requests={6, 9}]
Elevator [direction=UP, status=RUNNING, currentFloor=4, nextFloor=6, requests={6, 9}]
Elevator [direction=UP, status=RUNNING, currentFloor=5, nextFloor=6, requests={6, 9}]
Elevator [direction=UP, status=RUNNING, currentFloor=6, nextFloor=6, requests={6, 9}]
reach floor 6
open door
close door
Elevator [direction=UP, status=RUNNING, currentFloor=7, nextFloor=9, requests={9}]
Elevator [direction=UP, status=RUNNING, currentFloor=8, nextFloor=9, requests={9}]
Elevator [direction=UP, status=RUNNING, currentFloor=9, nextFloor=9, requests={9}]
reach floor 9
open door
close door

感觉和实际电梯有点像,就是没有动画演示。
如果每个楼层的按钮有方向的话,其实重点改造位置还是在findNextRequestFloor,以及在receiveRequest的操作,数据结构上原先的requests(BitSet)可能不够,要分成上行请求/下行请求等。具体这里就不实现了。

,