记得刚开始学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)可能不够,要分成上行请求/下行请求等。具体这里就不实现了。