-
Notifications
You must be signed in to change notification settings - Fork 4
Submission A #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Submission A #6
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| package org.togetherjava.event.elevator.elevators; | ||
|
|
||
| import java.util.StringJoiner; | ||
| import java.util.*; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
|
|
||
| /** | ||
|
|
@@ -17,6 +17,12 @@ public final class Elevator implements ElevatorPanel { | |
| private final int floorsServed; | ||
| private int currentFloor; | ||
|
|
||
| private final List<Integer> floorRequests = new ArrayList<>(); | ||
|
|
||
| public List<Integer> getFloorRequests() { | ||
| return Collections.unmodifiableList(floorRequests); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new elevator. | ||
| * | ||
|
|
@@ -52,18 +58,39 @@ public int getFloorsServed() { | |
| return floorsServed; | ||
| } | ||
|
|
||
| public int getTopFloor() { | ||
| return minFloor + floorsServed - 1; | ||
| } | ||
|
|
||
| @Override | ||
| public int getCurrentFloor() { | ||
| return currentFloor; | ||
| } | ||
|
|
||
| @Override | ||
| public void requestDestinationFloor(int destinationFloor) { | ||
| public synchronized void requestDestinationFloor(int destinationFloor) { | ||
| // TODO Implement. This represents a human or the elevator system | ||
| // itself requesting this elevator to eventually move to the given floor. | ||
| // The elevator is supposed to memorize the destination in a way that | ||
| // it can ensure to eventually reach it. | ||
| System.out.println("Request for destination floor received"); | ||
| if (floorRequests.contains(destinationFloor)) { | ||
| return; | ||
| } | ||
| floorRequests.add(destinationFloor); | ||
| } | ||
|
|
||
| public void incrementFloorByOne() { | ||
| if (currentFloor+1 > this.getTopFloor()) { | ||
| return; | ||
| } | ||
| currentFloor++; | ||
| } | ||
|
|
||
| public void decrementFloorByOne() { | ||
| if (currentFloor-1 < minFloor) { | ||
| return; | ||
| } | ||
| currentFloor--; | ||
|
Comment on lines
+82
to
+93
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use of math.min/max would be more idiomatic but ok |
||
| } | ||
|
|
||
| public void moveOneFloor() { | ||
|
|
@@ -76,7 +103,23 @@ public void moveOneFloor() { | |
| // meaning that the average time waiting (either in corridor or inside the elevator) | ||
| // is minimized across all humans. | ||
| // It is essential that this method updates the currentFloor field accordingly. | ||
| System.out.println("Request to move a floor received"); | ||
| if (floorRequests.isEmpty()) { | ||
| return; //stand still | ||
| } | ||
|
|
||
| //if we the target is up, we go up, if it is down, we go down | ||
| if (currentFloor < floorRequests.getFirst()) { //first come, first served, for now... | ||
| this.incrementFloorByOne(); | ||
| } else if (currentFloor > floorRequests.getFirst()) { | ||
| this.decrementFloorByOne(); | ||
| } | ||
|
|
||
| //if we have arrived at our floor, or we already are there, we remove the request | ||
| if (currentFloor == floorRequests.getFirst()) { | ||
| synchronized (this) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (the sync placement looks correct, neat) |
||
| floorRequests.removeFirst(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,9 @@ | |
| import org.togetherjava.event.elevator.humans.ElevatorListener; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| /** | ||
| * System controlling all elevators of a building. | ||
|
|
@@ -28,7 +30,40 @@ public void registerElevatorListener(ElevatorListener listener) { | |
| * Upon calling this, the system is ready to receive elevator requests. Elevators may now start moving. | ||
| */ | ||
| public void ready() { | ||
| elevatorListeners.forEach(listener -> listener.onElevatorSystemReady(this)); | ||
| elevatorListeners.parallelStream().forEach(listener -> listener.onElevatorSystemReady(this)); | ||
| } | ||
|
|
||
| public static int floorAndElevatorDistance(int floor, Elevator elevator) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. method naming is confusing |
||
| return Math.abs(elevator.getCurrentFloor()-floor); | ||
| } | ||
| @Override | ||
| public Elevator bestElevator(int atFloor, TravelDirection desiredTravelDirection) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. method naming is confusing |
||
| assert !elevators.isEmpty(); | ||
| Elevator bestElevator = elevators.getFirst(); | ||
| if (elevators.size() == 1) { | ||
| return bestElevator; | ||
| } | ||
| Set<Elevator> candidates = new HashSet<>(); //we find all the closest candidates | ||
| candidates.add(bestElevator); | ||
| for (var elevator : elevators) { | ||
| if (floorAndElevatorDistance(atFloor, elevator) < floorAndElevatorDistance(atFloor, candidates.stream().findAny().get())) { | ||
| candidates.clear(); | ||
| candidates.add(elevator); | ||
| } else if (floorAndElevatorDistance(atFloor, elevator) == floorAndElevatorDistance(atFloor, candidates.stream().findAny().get())) { | ||
| candidates.add(elevator); | ||
|
Comment on lines
+48
to
+53
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this could benefit from some smaller improvements. for example saving the method call results in vars, using orElseThrow() instead of get() and some other smaller things maybe |
||
| } | ||
| } | ||
| bestElevator = candidates.stream().findAny().get(); | ||
| for (var elevator : candidates) { | ||
| if (elevator.getFloorRequests().size() < bestElevator.getFloorRequests().size()) { //out of the closest candidates, we chose the one with the least traffic | ||
| bestElevator = elevator; | ||
| } | ||
| } | ||
| return bestElevator; | ||
| } | ||
|
|
||
| public void requestElevator(Elevator elevator, int atFloor) { | ||
| elevator.requestDestinationFloor(atFloor); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -39,11 +74,11 @@ public void requestElevator(int atFloor, TravelDirection desiredTravelDirection) | |
| // The human can then enter the elevator and request their actual destination within the elevator. | ||
| // Ideally this has to select the best elevator among all which can reduce the time | ||
| // for the human spending waiting (either in corridor or in the elevator itself). | ||
| System.out.println("Request for elevator received"); | ||
| bestElevator(atFloor, desiredTravelDirection).requestDestinationFloor(atFloor); | ||
| } | ||
|
|
||
| public void moveOneFloor() { | ||
| elevators.forEach(Elevator::moveOneFloor); | ||
| elevators.forEach(elevator -> elevatorListeners.forEach(listener -> listener.onElevatorArrivedAtFloor(elevator))); | ||
| elevators.parallelStream().forEach(Elevator::moveOneFloor); | ||
| elevators.parallelStream().forEach(elevator -> elevatorListeners.parallelStream().forEach(listener -> listener.onElevatorArrivedAtFloor(elevator))); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
| * The system in corridors that allows requesting elevators to the current floor. | ||
| */ | ||
| public interface FloorPanelSystem { | ||
|
|
||
| /** | ||
| * Requests an elevator to move to the given floor to pick up a human. | ||
| * | ||
|
|
@@ -14,4 +15,6 @@ public interface FloorPanelSystem { | |
| * requesting that an elevator comes to pick them up for travel into the given direction. | ||
| */ | ||
| void requestElevator(int atFloor, TravelDirection desiredTravelDirection); | ||
| void requestElevator(Elevator bestElevator, int atFloor); | ||
| Elevator bestElevator(int atFloor, TravelDirection desiredTravelDirection); | ||
|
Comment on lines
+18
to
+19
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing javadoc |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,12 @@ | ||
| package org.togetherjava.event.elevator.humans; | ||
|
|
||
| import org.togetherjava.event.elevator.elevators.Elevator; | ||
| import org.togetherjava.event.elevator.elevators.ElevatorPanel; | ||
| import org.togetherjava.event.elevator.elevators.FloorPanelSystem; | ||
| import org.togetherjava.event.elevator.elevators.TravelDirection; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.OptionalInt; | ||
| import java.util.StringJoiner; | ||
|
|
||
|
|
@@ -17,12 +21,13 @@ public final class Human implements ElevatorListener { | |
| private State currentState; | ||
| private final int startingFloor; | ||
| private final int destinationFloor; | ||
| private Elevator bestElevator = null; | ||
| /** | ||
| * If the human is currently inside an elevator, this is its unique ID. | ||
| * Otherwise, this is {@code null} to indicate that the human is currently on the corridor. | ||
| */ | ||
| private Integer currentEnteredElevatorId; | ||
|
|
||
| private final List<HumanArrivedListener> listeners = new ArrayList<>(); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. design-wise this is a bit weird. like, why would humans contact anyone when they arrived. more reasonable might be something like an elevator notifying the elevator system when a human leaves the elevator. |
||
| /** | ||
| * Creates a new human. | ||
| * <p> | ||
|
|
@@ -43,6 +48,20 @@ public Human(int startingFloor, int destinationFloor) { | |
| currentState = State.IDLE; | ||
| } | ||
|
|
||
| public void addListener(HumanArrivedListener listener) { | ||
| this.listeners.add(listener); | ||
| } | ||
|
|
||
| private void setArrived() { | ||
| if (currentState == State.ARRIVED) { | ||
| return; //dont want to notify listeners again for our arrival | ||
| } | ||
| this.currentState = State.ARRIVED; | ||
| for (var listener : listeners) { | ||
| listener.onHumanArrived(this); | ||
| } | ||
|
Comment on lines
+56
to
+62
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this logic is easy to accidentally miss by directly setting the state. if such a logic is used, all state-changes should flow through a setter, not just for ARRIVED |
||
| } | ||
|
|
||
| public State getCurrentState() { | ||
| return currentState; | ||
| } | ||
|
|
@@ -60,7 +79,20 @@ public void onElevatorSystemReady(FloorPanelSystem floorPanelSystem) { | |
| // TODO Implement. The system is now ready and the human should leave | ||
| // their initial IDLE state, requesting an elevator by clicking on the buttons of | ||
| // the floor panel system. The human will now enter the WAITING_FOR_ELEVATOR state. | ||
| System.out.println("Ready-event received"); | ||
| if (this.getCurrentState() != State.IDLE) { | ||
| return; | ||
| } | ||
| this.currentState = State.WAITING_FOR_ELEVATOR; | ||
| if (destinationFloor == startingFloor) { | ||
| return; | ||
| } | ||
| bestElevator = floorPanelSystem.bestElevator(destinationFloor, destinationFloor > startingFloor ? TravelDirection.UP : TravelDirection.DOWN); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. method naming could be improved. its also unclear that this is essentially a getter/compute method as its missing any verb |
||
| if(bestElevator.getCurrentFloor() == startingFloor) { | ||
| this.currentEnteredElevatorId = bestElevator.getId(); | ||
| this.currentState = State.TRAVELING_WITH_ELEVATOR; | ||
| return; | ||
| } | ||
| floorPanelSystem.requestElevator(bestElevator,startingFloor); | ||
|
Comment on lines
+89
to
+95
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this works but with respect to real life thats not a thing. humans dont get told which elevator to use and wait for, they press a button and when the door opens they enter, regardless of elevator ID |
||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -70,7 +102,23 @@ public void onElevatorArrivedAtFloor(ElevatorPanel elevatorPanel) { | |
| // elevator and request their actual destination floor. The state has to change to TRAVELING_WITH_ELEVATOR. | ||
| // If the human is currently traveling with this elevator and the event represents | ||
| // arrival at the human's destination floor, the human can now exit the elevator. | ||
| System.out.println("Arrived-event received"); | ||
| if (this.getCurrentState() == State.ARRIVED || | ||
| (this.getDestinationFloor() != elevatorPanel.getCurrentFloor() && this.getStartingFloor() != elevatorPanel.getCurrentFloor())) { | ||
| return; | ||
| } | ||
| //are we on our destination floor or is our elevator at our destination floor? hop out | ||
| if (startingFloor == destinationFloor || (destinationFloor == elevatorPanel.getCurrentFloor() && this.currentEnteredElevatorId != null && this.currentEnteredElevatorId == elevatorPanel.getId())) { | ||
| this.currentEnteredElevatorId = null; | ||
| this.setArrived(); | ||
| return; | ||
| } | ||
| assert bestElevator != null; | ||
| //elevator's in our floor and we arent traveling? hop in | ||
| if (startingFloor == elevatorPanel.getCurrentFloor() && this.getCurrentState() != State.TRAVELING_WITH_ELEVATOR) { | ||
| this.currentState = State.TRAVELING_WITH_ELEVATOR; | ||
| this.currentEnteredElevatorId = elevatorPanel.getId(); | ||
| elevatorPanel.requestDestinationFloor(destinationFloor); | ||
| } | ||
| } | ||
|
|
||
| public OptionalInt getCurrentEnteredElevatorId() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package org.togetherjava.event.elevator.humans; | ||
|
|
||
| @FunctionalInterface | ||
| public interface HumanArrivedListener { | ||
| void onHumanArrived(Human human); | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
| import org.togetherjava.event.elevator.elevators.Elevator; | ||
| import org.togetherjava.event.elevator.elevators.ElevatorSystem; | ||
| import org.togetherjava.event.elevator.humans.Human; | ||
| import org.togetherjava.event.elevator.humans.HumanArrivedListener; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
|
|
@@ -12,12 +13,13 @@ | |
| import java.util.stream.LongStream; | ||
| import java.util.stream.Stream; | ||
|
|
||
| public final class Simulation { | ||
| public final class Simulation implements HumanArrivedListener { | ||
| private final List<Human> humans; | ||
| private final List<Elevator> elevators; | ||
| private final ElevatorSystem elevatorSystem; | ||
| private final View view; | ||
| private long stepCount; | ||
| private long humanTravelingCount; | ||
| private final List<HumanStatistics> humanStatistics; | ||
|
|
||
| public static Simulation createSingleElevatorSingleHumanSimulation() { | ||
|
|
@@ -71,10 +73,15 @@ public Simulation(List<Elevator> elevators, List<Human> humans) { | |
|
|
||
| elevatorSystem = new ElevatorSystem(); | ||
| this.elevators.forEach(elevatorSystem::registerElevator); | ||
| this.humans.forEach(elevatorSystem::registerElevatorListener); | ||
| this.humans.forEach(human -> { | ||
| elevatorSystem.registerElevatorListener(human); | ||
| human.addListener(this); | ||
| }); | ||
|
|
||
| humanStatistics = this.humans.stream().map(HumanStatistics::new).toList(); | ||
| view = new View(this); | ||
|
|
||
| this.humanTravelingCount = humans.size(); | ||
| } | ||
|
|
||
| public void startAndExecuteUntilDone(int stepLimit) { | ||
|
|
@@ -102,9 +109,20 @@ public void step() { | |
| } | ||
|
|
||
| public boolean isDone() { | ||
| return humans.stream() | ||
| .map(Human::getCurrentState) | ||
| .allMatch(Human.State.ARRIVED::equals); | ||
| return humanTravelingCount == 0; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (prob an optimization to avoid big iterations) |
||
| } | ||
|
|
||
| public void addHuman(Human human) { | ||
| if (isDone()) { | ||
| throw new SimulationFinishedException("Can't add new human after simulation is finished!"); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. okay, but a IllegalStateException might have been more appropriate. but shows they can use exceptions, neat |
||
| } | ||
| humans.add(human); | ||
| elevatorSystem.registerElevatorListener(human); | ||
| human.addListener(this); | ||
| human.onElevatorSystemReady(elevatorSystem); | ||
| if (human.getCurrentState() != Human.State.ARRIVED) { | ||
| humanTravelingCount++; | ||
| } | ||
|
Comment on lines
+123
to
+125
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isnt the initial state always not-arrived by definition? smells a bit |
||
| } | ||
|
|
||
| public long getStepCount() { | ||
|
|
@@ -136,20 +154,26 @@ public void printResult() { | |
|
|
||
| System.out.println("Median time spend per state:"); | ||
| for (Human.State state : Human.State.values()) { | ||
| int averagePercentage = getAverageTimePercentageSpendForState(state); | ||
| System.out.printf("\t%s: %d%%%n", state, averagePercentage); | ||
| double averagePercentage = getAverageTimePercentageSpendForState(state); | ||
| System.out.printf("\t%s: %f%%%n", state, averagePercentage); | ||
| } | ||
| } | ||
|
|
||
| public int getAverageTimePercentageSpendForState(Human.State state) { | ||
| public double getAverageTimePercentageSpendForState(Human.State state) { | ||
| LongStream sortedSteps = humanStatistics.stream() | ||
| .mapToLong(stats -> stats.stepsForState(state)) | ||
| .sorted(); | ||
| long medianSteps = humanStatistics.size() % 2 == 0 | ||
| ? (long) sortedSteps.skip(humanStatistics.size() / 2 - 1).limit(2).average().orElseThrow() | ||
| : sortedSteps.skip(humanStatistics.size() / 2).findFirst().orElseThrow(); | ||
|
|
||
| long medianPercentage = 100 * medianSteps / stepCount; | ||
| return (int) medianPercentage; | ||
| return (double) (100 * medianSteps) / stepCount; | ||
| } | ||
|
|
||
| @Override | ||
| public synchronized void onHumanArrived(Human human) { | ||
| if (humanTravelingCount > 0) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. defensive behavior smells, why not an exception? does this actually happen? |
||
| humanTravelingCount--; //We trust that the human hasnt notified us twice. | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. kek. but okay |
||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a queue would prob be more appropriate