Amazon Interview Question
Software Engineer / DevelopersCountry: United States
Interview Type: In-Person
My idea is to make Elevator implement Runnable, and constantly check a queue (linkedList). A Controller class will assign which floor to go in the queue. When the queue is empty, the run() method will wait (queue.wait() ), when a floor is assigned to this elevator, it will call queue.notify() to wake up the run() method, and run() method will call goToFloor(queue.pop())
I just figured my approach of having each elevator to have a queue and trying to synchronize and taking timing issue (the time for an elevator to get to a floor) will make this problem very complex. I tried to write it on paper, but dont think it works. It seems like we don't really need to take concurrency or timing issue into account here, but we do need to somehow use a queue to distribute the control. Any suggestion?
(I'm OP)
Some fixes:
boolean isStarted();
Passenger
Yeah, I guess you could use synchronization for this as well.
Given your solution doesn't handle synchronization and timing, which I believe is good enough for a 45-min interview, how do you distribute the control? Like how does your system notify the closest or the first available found elevator to go to certain floor, and how do you use the priorityQueue?
I think you can use the priority queue in this way: Let the elevator be at floor 5 going down. If one were on the second floor trying to go up and another on the tenth floor who want to go down, the elevator would choose the second floor first because it has a higher priority in the queue than someone who wants to go up. The processes are not pre-empted because if someone hit the button on the first floor, it would still go to the second floor first, even though the first may have a higher priority. The elevator just adds new processes to the ready queue.
So, you can add a floorNumber with a certain priority whenever the passenger wants to go somewhere.
I don't think the system needs to notify other elevators of anything?
I agree mostly about all.
But I think we should have a class call ElevatorSystem that has a Set of Elevators.
ElevatorSystem must be a singleton object where you control each Elevator.
Additional business rules could be:
-> There are a window of time where the elevator are very crowded, so If there are N Elevators and M floors. It could be good to N/2 elevators only server requests for floors from 0 to M/2 and the rest from M/2 + 1 to M
-> If 2 elevators at floor 10 are going down, and there is a request at floor 5th, then one of them will have to attend the request, but not both. which one? Well It may be one that is carries less people perhaps.
-> Each Elevator must contain a priority attribute that can be use to take decisions of requests.
We want to avoid any synchronization issue, so access to Queue<Request> reqs must be synchronized.
We can even make the system distributed and treat each elevator as a Peer, Host or Computer. So we can send messages back and forth.
For example:
Request at floor 5:
->Send broadcast to all Elevators
-> Each elevator that receives the messages, sent back with an reply message with Its state ( location, occupation, weight, etc etc ) And let the System decide which one to call.
We can discuss a lot about this type of questions. I think It is good to share ideas as much as possible.
*repose with format corrected*
public class Elevator {
public static final int MAX_FLOORS = 50;
// upward floor queue
private PriorityQueue<Integer> floors_up = new PriorityQueue<Integer>(MAX_FLOORS);
// downward floor queue
private PriorityQueue<Integer> floors_down = new PriorityQueue<Integer>(MAX_FLOORS, new Comparator<Integer>(){
public int compare(Integer arg0, Integer arg1) {
return arg1.compareTo(arg0);
}
});
// effective floor queue
private PriorityQueue<Integer> floors = floors_up;
// current floor
private int current = 0;
public void userCallAt(int where) {
setTarget(where);
}
public void setTarget(int target) {
// ignore if target is current
if (target == current) {
return;
}
// add target to right queue
if (floors == floors_up) {
if (target < current) {
floors_down.offer(target);
} else {
floors_up.offer(target);
}
} else {
if (target > current) {
floors_up.offer(target);
} else {
floors_down.offer(target);
}
}
// swap queue to turn around the elevator
if (floors.isEmpty()) {
floors = (floors == floors_up ? floors_down : floors_up);
}
}
public int getTarget() {
if (!floors.isEmpty()) {
return floors.peek();
}
if (floors_up.isEmpty() && floors_down.isEmpty()) {
return current;
}
floors = (floors == floors_up ? floors_down : floors_up);
return floors.peek();
}
public int getCurrent() {
return current;
}
public void step(int target) {
if (target > current) {
current++;
} else {
current--;
}
}
public void stop() {
floors.poll();
}
public void move() {
while (getCurrent() != getTarget()) {
do {
step(getTarget());
} while (getCurrent() != getTarget());
stop();
}
}
}
I think instead of having 2 priority queues, you can just use combination of direction and vector<floor> where floor could be integer/boolean type.
if direction is up, you serve floors in increasing order otherwise decreasing.
Someone requests elevator on floor X, set value at index X in vector to ON
Also, when elevator stops at some floor and people getting in to the elevator request for target floor. People (already)inside an elevator can request floor at any time.
(Also with each floor, we can link # of ppl requested for particular floor at any given time)
As you serve floors, reset it in vector to OFF. If people inside an elevator are linked to a floor, then clear that list now.
I look at it as a producer/consumer problem. So as others mentioned, synchronization is required.
I think we dont need so much variables and methods ... as per my approach
we need
enum Motion
{
Going_Up, Going_Down, Stoped
}
Class Lift
{
int current Floor;
int priorityQueue nextFloorToStop;
int LiftId;
}
Class LiftController
{
array of lifts;
queue Allrequests;
public void CallLift(int floorIndex)
{
}
public void int NearestLift(int floorIndex)
{
}
public void bool IsMovingInSameDirection(int liftId, int FloorIndex)
{
}
}
If No lift is available to take request then add that request in AllRequest queue else add it in lift's queue.
When when lift comes to stop (after completing its all floor stops) then it will notify controller class to
check if there is any request pending in "allRequest Queue".
public class Elevator {
public static final int MAX_FLOORS = 50;
// upward floor queue
private PriorityQueue<Integer> floors_up = new PriorityQueue<Integer>(MAX_FLOORS);
// downward floor queue
private PriorityQueue<Integer> floors_down = new PriorityQueue<Integer>(MAX_FLOORS, new Comparator<Integer>(){
public int compare(Integer arg0, Integer arg1) {
return arg1.compareTo(arg0);
}
});
// effective floor queue
private PriorityQueue<Integer> floors = floors_up;
// current floor
private int current = 0;
public void userCallAt(int where) {
setTarget(where);
}
public void setTarget(int target) {
// ignore if target is current
if (target == current) {
return;
}
// add target to right queue
if (floors == floors_up) {
if (target < current) {
floors_down.offer(target);
} else {
floors_up.offer(target);
}
} else {
if (target > current) {
floors_up.offer(target);
} else {
floors_down.offer(target);
}
}
// swap queue to turn around the elevator
if (floors.isEmpty()) {
floors = (floors == floors_up ? floors_down : floors_up);
}
}
public int getTarget() {
if (!floors.isEmpty()) {
return floors.peek();
}
if (floors_up.isEmpty() && floors_down.isEmpty()) {
return current;
}
floors = (floors == floors_up ? floors_down : floors_up);
return floors.peek();
}
public int getCurrent() {
return current;
}
public void step(int target) {
if (target > current) {
current++;
} else {
current--;
}
}
public void stop() {
floors.poll();
}
public void move() {
while (getCurrent() != getTarget()) {
do {
step(getTarget());
} while (getCurrent() != getTarget());
stop();
}
}
}
*code for demo idea*
Direction {
UP, DOWN
}
Elevator {
Elevator id;
Direction currentDirection;
int currentLevel;
List<MovementPlan> pendingPlans;
MovementPlan currentPlan;
// Definite stop in route. Create a new plan if required.
public void addStoppage(int level) {
boolean isPossibleInCurrentPlan = currentPlan.addStop(level, currentDirection);
if (isPossibleInCurrentPlan) {
return;
}
// try to see if it can fit in other plans
for (MovementPlan plan : pendingPlans) {
isPossibleInPending = plan.addStop(level, currentDirection);
if(isPossibleInPending) return;
}
// Make a new plan. Current plan didn't work means request is of opposite direction
Direction oppositeDirection = direction.equals(UP)?DOWN:UP;
Integer firstStop = currentPlan.getLast(); // last stop there is first stop here
Set<Integer> stops = {firstStop, level};
MovementPlan newPlan = new MovementPlan(stops, oppositeDirection);
plans.add(newPlan);
}
public boolean isIdle() { // if currentPlan==null && pendingPlans.isEmpty() }
// Should keep executing asynchronously
public void move() {
while(true) {
moveTo(currentPlan.getNext());
refreshCurrentLevel();
// assuming movement completed
currentPlan.reachedNextStop();
if (currentPlan.isCompleted()) {
if (pendingPlans.isEmpty()) {
// wait for new plan
} else {
currentPlan = pendingPlans.get(0);
}
}
}
}
}
MovementPlan {
Direction direction;
Set<Integer> assignedStoppages;
public ElevationPlan(Set<Integer assignedStops, Direction direction) {
// keep assigned stops in sorted order.
// ascending if direction is UP, else descending if direction is DOWN
}
public Integer getNext() {
assignedStoppages.get(0);
}
public Integer getLast() {
assignedStoppages.get(assignedStoppages.size() -1);
}
// Adds if new stop falls in the way. Else returns false
public boolean addStop(int newStop, Direction direction) {
// is elevator moving in same direction
boolean isAlignedWithPlan = ((direction.equals(UP) && newStop > getNext())
|| (direction.equals(DOWN)&& newStop < getNext()))
if ((!isCompleted()) && this.direction.equals(direction) && isAlignedWithPlan)
assignedStoppages.add(newStop);
else
return false;
}
public Integer reachedNextStop() {
Integer next = getNext();
assignedStoppages.remove(next);
}
public boolean isCompleted() {
return assignedStoppages.isEmpty();
}
}
ExternalController {
List<Elevator> elevators;
initialize(Set<Elevator> elevators) {
// initialize
}
public void addPickRequest(int stopAtLevel, Direction direction) {
// get all current movement plans from all elevators
// Check if already assigned to one of the elevators
// else pick up next idle elevator and call addStoppage there
// else if no elevator is idle
// pick one elevator and call addStoppage there. will internally create new movement plan if necessary
}
}
There's probably something else I'm missing but I think I have some idea...
- Anonymous February 11, 2014