Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions Contest/Assignment/src/org/togetherjava/event/elevator/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ public static void main(final String[] args) {
// Eventually try out the randomly generated systems. If you want to debug a problem you encountered
// with one of them, note down the seed that it prints at the beginning and then use the variant that takes this seed.
// That way, it will generate the same system again, and you can repeat the test.
Simulation simulation = Simulation.createSingleElevatorSingleHumanSimulation();
//Simulation simulation = Simulation.createRandomSimulation(5, 50, 10);
// Simulation simulation = Simulation.createSimpleSimulation();
// Simulation simulation = Simulation.createRandomSimulation(5, 50, 10);
// Simulation simulation = Simulation.createRandomSimulation(putDesiredSeedHere, 5, 50, 10);

Simulation simulation = Simulation.createRandomSimulation(3, 10, 100_000, 200);
//Simulation simulation = Simulation.createRandomSimulation(2, 20, 1_000, 50);
//Simulation simulation = Simulation.createRandomSimulation(-806872529110342439L, 200, 50000, 1000);
//Simulation simulation = Simulation.createRandomSimulation(4637787693156730566L,5, 5_000, 100);
simulation.printSummary();

System.out.println("Starting simulation...");
Expand All @@ -33,8 +36,10 @@ public static void main(final String[] args) {
while (!simulation.isDone()) {
System.out.println("\tSimulation step " + simulation.getStepCount());
simulation.step();
simulation.prettyPrint();

//simulation.prettyPrint();
if (simulation.getStepCount() == 5000) {
System.out.println();
}
if (simulation.getStepCount() >= 100_000) {
throw new IllegalStateException("Simulation aborted. All humans should have arrived"
+ " by now, but they did not. There is likely a bug in your code.");
Expand Down
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;

/**
Expand All @@ -17,6 +17,12 @@ public final class Elevator implements ElevatorPanel {
private final int floorsServed;
private int currentFloor;

private final List<Integer> floorRequests = new ArrayList<>();
Copy link
Member Author

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


public List<Integer> getFloorRequests() {
return Collections.unmodifiableList(floorRequests);
}

/**
* Creates a new elevator.
*
Expand Down Expand Up @@ -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
Copy link
Member Author

Choose a reason for hiding this comment

The 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() {
Expand All @@ -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) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(the sync placement looks correct, neat)

floorRequests.removeFirst();
}
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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) {
Copy link
Member Author

Choose a reason for hiding this comment

The 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) {
Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Member Author

Choose a reason for hiding this comment

The 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
Expand All @@ -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
Expand Up @@ -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.
*
Expand All @@ -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
Copy link
Member Author

Choose a reason for hiding this comment

The 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;

Expand All @@ -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<>();
Copy link
Member Author

Choose a reason for hiding this comment

The 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.
but even that is technically IRL not a thing - anyways, fair solution to the performance problem.

/**
* Creates a new human.
* <p>
Expand All @@ -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
Copy link
Member Author

Choose a reason for hiding this comment

The 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;
}
Expand All @@ -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);
Copy link
Member Author

@Zabuzard Zabuzard Dec 1, 2025

Choose a reason for hiding this comment

The 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
Copy link
Member Author

Choose a reason for hiding this comment

The 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
Expand All @@ -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() {
Expand Down
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
Expand Up @@ -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;
Expand All @@ -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() {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -102,9 +109,20 @@ public void step() {
}

public boolean isDone() {
return humans.stream()
.map(Human::getCurrentState)
.allMatch(Human.State.ARRIVED::equals);
return humanTravelingCount == 0;
Copy link
Member Author

Choose a reason for hiding this comment

The 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!");
Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Member Author

Choose a reason for hiding this comment

The 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() {
Expand Down Expand Up @@ -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) {
Copy link
Member Author

Choose a reason for hiding this comment

The 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.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kek. but okay

}
}
}
Loading