From 4fad0a377e7b2758b33d5e6a5f71150bbd5d2917 Mon Sep 17 00:00:00 2001 From: Arne Keller Date: Wed, 4 Mar 2020 22:22:05 +0100 Subject: [PATCH] Optimize large train placement --- fuzz9_input.txt | 1 + fuzz9_output.txt | 1 + long_train_input.txt | 13 ++ long_train_output.txt | 13 ++ src/edu/kit/informatik/MainTest.java | 5 + src/edu/kit/informatik/model/Rail.java | 19 +- .../kit/informatik/model/RailwayNetwork.java | 31 ++- src/edu/kit/informatik/model/Switch.java | 23 +- src/edu/kit/informatik/model/Track.java | 4 + src/edu/kit/informatik/model/Train.java | 204 ++++++++---------- .../kit/informatik/model/TrainManager.java | 9 +- src/edu/kit/informatik/model/Vector2D.java | 11 + 12 files changed, 184 insertions(+), 150 deletions(-) create mode 100644 long_train_input.txt create mode 100644 long_train_output.txt diff --git a/fuzz9_input.txt b/fuzz9_input.txt index 0eb80e4..37c0a7f 100644 --- a/fuzz9_input.txt +++ b/fuzz9_input.txt @@ -3,6 +3,7 @@ add track (0,0) -> (-2147483648,0) list tracks create engine electrical T3 Marie 1 false true add train 1 T3-Marie +put train 1 at (0,0) in direction 0,-1 put train 1 at (0,0) in direction -1,0 step 1000 delete train 1 diff --git a/fuzz9_output.txt b/fuzz9_output.txt index 6b48fb0..9fd7f64 100644 --- a/fuzz9_output.txt +++ b/fuzz9_output.txt @@ -4,6 +4,7 @@ t 1 (2147483647,0) -> (0,0) 2147483647 t 2 (0,0) -> (-2147483648,0) 2147483648 T3-Marie electrical engine T3-Marie added to train 1 +Error, could not place train OK Train 1 at (-1000,0) OK diff --git a/long_train_input.txt b/long_train_input.txt new file mode 100644 index 0000000..a13c8b8 --- /dev/null +++ b/long_train_input.txt @@ -0,0 +1,13 @@ +add track (2147483647,0) -> (0,0) +add track (0,0) -> (-2147483648,0) +list tracks +create engine electrical T3 Marie 2147483647 false true +create coach passenger 2147483647 true false +add train 1 T3-Marie +add train 1 W1 +put train 1 at (-2147483647,0) in direction -1,0 +step 1 +step 1 +put train 1 at (-2147483647,0) in direction -1,0 +step -1 +exit diff --git a/long_train_output.txt b/long_train_output.txt new file mode 100644 index 0000000..666ee5c --- /dev/null +++ b/long_train_output.txt @@ -0,0 +1,13 @@ +1 +2 +t 1 (2147483647,0) -> (0,0) 2147483647 +t 2 (0,0) -> (-2147483648,0) 2147483648 +T3-Marie +1 +electrical engine T3-Marie added to train 1 +passenger coach W1 added to train 1 +OK +Train 1 at (-2147483648,0) +Crash of train 1 +OK +Crash of train 1 diff --git a/src/edu/kit/informatik/MainTest.java b/src/edu/kit/informatik/MainTest.java index c99f03a..be65b3e 100644 --- a/src/edu/kit/informatik/MainTest.java +++ b/src/edu/kit/informatik/MainTest.java @@ -113,6 +113,11 @@ class MainTest { cmpInOut("newest_ilias_shit_input.txt", "newest_ilias_shit_output.txt"); } + @Test + void long_train() throws IOException { + cmpInOut("long_train_input.txt", "long_train_output.txt"); + } + private void cmpInOut(String in, String out) throws IOException { System.setIn(new ByteArrayInputStream(readFile(in))); ByteArrayOutputStream output = new ByteArrayOutputStream(); diff --git a/src/edu/kit/informatik/model/Rail.java b/src/edu/kit/informatik/model/Rail.java index 5f3f5c1..0515cd2 100644 --- a/src/edu/kit/informatik/model/Rail.java +++ b/src/edu/kit/informatik/model/Rail.java @@ -87,11 +87,20 @@ public abstract class Rail { public abstract boolean allowsDirectionFrom(Vector2D position, Vector2D direction); /** - * - * @param position - * @param direction - * @param steps - * @return + * Move a position along this rail in the specified direction for the specified amount of steps. This method will + * stop at the end of the rail, even if that will omit some steps. + * + * @param position position to move from + * @param direction direction to go + * @param steps amount of steps + * @return moved position */ public abstract Vector2D move(Vector2D position, Vector2D direction, long steps); + + /** + * Calculate the (Manhattan) length of this rail. + * + * @return the length of this rail + */ + public abstract long getLength(); } diff --git a/src/edu/kit/informatik/model/RailwayNetwork.java b/src/edu/kit/informatik/model/RailwayNetwork.java index aca7c21..a2db194 100644 --- a/src/edu/kit/informatik/model/RailwayNetwork.java +++ b/src/edu/kit/informatik/model/RailwayNetwork.java @@ -1,14 +1,13 @@ package edu.kit.informatik.model; -import edu.kit.informatik.Terminal; import edu.kit.informatik.ui.InvalidInputException; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Queue; import java.util.Set; import java.util.stream.Collectors; @@ -198,20 +197,22 @@ public class RailwayNetwork { /** * Move a position along the rails of this rail network in the specified direction. + * * @param position the position to move * @param direction has to be (0,1) (0,-1) (1,0) (-1,0), will be modified if turn is necessary - * @return next position, null if off the rails + * @param steps amount of steps to go + * @return next position, null if off the rails after single step */ - public Vector2D move(Vector2D position, Vector2D direction) { + public Vector2D move(Vector2D position, Vector2D direction, long steps) { Rail containingRail = findContainingRail(position); if (containingRail != null) { - return position.add(direction); + return containingRail.move(position, direction, steps); } Rail[] touchingRails = findTouchingRails(position); Vector2D nextPossiblePosition = position.add(direction); Rail possibleContainingRail = findRailPotentiallyContaining(nextPossiblePosition); if (possibleContainingRail != null && !possibleContainingRail.contains(position.subtract(direction))) { - return possibleContainingRail.move(position, direction, 1); + return possibleContainingRail.move(position, direction, steps); } if (touchingRails.length == 0) { @@ -219,8 +220,8 @@ public class RailwayNetwork { } else if (touchingRails.length == 1) { return touchingRails[0].move(position, direction, 1); } - Vector2D onRailOne = touchingRails[0].move(position, new Vector2D(direction), 1); - Vector2D onRailTwo = touchingRails[1].move(position, new Vector2D(direction), 1); + Vector2D onRailOne = touchingRails[0].move(position, new Vector2D(direction), steps); + Vector2D onRailTwo = touchingRails[1].move(position, new Vector2D(direction), steps); if (position.equals(onRailOne) || onRailOne == null) { // we are moving on rail two Vector2D newDirection = touchingRails[1].getDirectionFrom(position); @@ -251,6 +252,20 @@ public class RailwayNetwork { return rails.values().stream().filter(rail -> rail.canContain(position)).findFirst().orElse(null); } + /** + * Find a rail that connects to or contains the first position and connects to or contains the second position. + * + * @param positionA point which the rail should touch or connect to + * @param positionB point which the rail should touch or connect to + * @return rail satisfying these conditions + */ + public Optional findRail(Vector2D positionA, Vector2D positionB) { + return rails.values().stream() + .filter(rail -> rail.connectsTo(positionA) || rail.contains(positionA)) + .filter(rail -> rail.connectsTo(positionB) || rail.contains(positionB)) + .findFirst(); + } + /** * Find all rails that *touch* (not contain) this position. * @param position the position to check diff --git a/src/edu/kit/informatik/model/Switch.java b/src/edu/kit/informatik/model/Switch.java index 24275fe..f2e30c2 100644 --- a/src/edu/kit/informatik/model/Switch.java +++ b/src/edu/kit/informatik/model/Switch.java @@ -2,7 +2,6 @@ package edu.kit.informatik.model; import edu.kit.informatik.ui.InvalidInputException; -import java.util.Arrays; import java.util.Objects; /** @@ -108,20 +107,6 @@ public final class Switch extends Rail { return selection.getDirectionFrom(position); } - private Vector2D[] getPossibleDirectionsFrom(Vector2D position) { - if (positionOne.canConnectTo(position) && positionTwo.canConnectTo(position)) { - return new Vector2D[] { - positionOne.getDirectionFrom(position), positionOne.getDirectionFrom(position).negated(), - positionTwo.getDirectionFrom(position), positionTwo.getDirectionFrom(position).negated()}; - } else if (positionOne.canConnectTo(position)) { - return new Vector2D[] {positionOne.getDirectionFrom(position)}; - } else if (positionTwo.canConnectTo(position)) { - return new Vector2D[] {positionTwo.getDirectionFrom(position)}; - } else { - return new Vector2D[0]; - } - } - @Override public boolean allowsDirectionFrom(Vector2D position, Vector2D direction) { return (selection != null && selection.contains(position)) @@ -129,6 +114,11 @@ public final class Switch extends Rail { || (positionTwo.connectsTo(position) && positionTwo.allowsDirectionFrom(position, direction)); } + @Override + public long getLength() { + return selection.getLength(); + } + @Override public String toString() { if (selection == null) { @@ -136,8 +126,7 @@ public final class Switch extends Rail { positionOne.getEnd(), positionTwo.getEnd()); } else { return String.format("s %d %s -> %s,%s %d", getIdentifier(), - positionOne.getStart(), positionOne.getEnd(), positionTwo.getEnd(), - selection.getLength()); + positionOne.getStart(), positionOne.getEnd(), positionTwo.getEnd(), getLength()); } } diff --git a/src/edu/kit/informatik/model/Track.java b/src/edu/kit/informatik/model/Track.java index ffb1465..feff003 100644 --- a/src/edu/kit/informatik/model/Track.java +++ b/src/edu/kit/informatik/model/Track.java @@ -58,6 +58,9 @@ public final class Track extends Rail { @Override public boolean contains(Vector2D position) { + if (position == null) { + return false; + } if (start.getX() == end.getX() && position.getX() == start.getX()) { int startY = start.getY(); int endY = end.getY(); @@ -130,6 +133,7 @@ public final class Track extends Rail { return new Vector2D(end); } + @Override public long getLength() { return start.distanceTo(end); } diff --git a/src/edu/kit/informatik/model/Train.java b/src/edu/kit/informatik/model/Train.java index e149b68..b2ff462 100644 --- a/src/edu/kit/informatik/model/Train.java +++ b/src/edu/kit/informatik/model/Train.java @@ -9,17 +9,16 @@ import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; -import java.util.stream.Stream; /** * A train consisting of one or more rolling stock. Can be placed on a rail network. * * @author Arne Keller - * @version 1.0 + * @version 1.1 */ public final class Train { /** - * Numerical identifer of this train. + * Numerical identifier of this train. */ private final int identifier; /** @@ -27,10 +26,13 @@ public final class Train { */ private final List rollingStocks = new ArrayList<>(); /** - * List of positions this train touches. This is a linked list because we only have to update the first and last - * positions when moving the train. + * List of positions this train occupies. More specifically, it only contains: + * the position of the front of the train, any positions on rail connections, the position of the back of the train + * + * This is a linked list because we only have to update + * the positions at the start and end of the list when moving the train. */ - private LinkedList position; + private LinkedList positions; /** * Set of rails this train occupies. */ @@ -127,33 +129,29 @@ public final class Train { */ public boolean isOnRail(final Rail rail) { return isPlaced() && (occupiedRails.stream().anyMatch(blockedRail -> blockedRail == rail) - || position.stream().anyMatch(rail::canConnectTo)); + || positions.stream().anyMatch(rail::canConnectTo)); } /** * Put a train on the rail network. + * * @param railNetwork rail network to place the train on * @param position where to place the train * @param rawDirection direction in which the train should initially go * @return whether the train was successfully placed - * @throws InvalidInputException when the train is too long to place */ - public boolean placeOn(RailwayNetwork railNetwork, Vector2D position, Vector2D rawDirection) - throws InvalidInputException { - Vector2D direction = rawDirection.normalized(); - Vector2D positioningDirection = direction.negated(); + public boolean placeOn(RailwayNetwork railNetwork, Vector2D position, Vector2D rawDirection) { + if (isPlaced()) { + return false; + } + final Vector2D direction = rawDirection.normalized(); + final Vector2D positioningDirection = direction.negated(); + // size of train that still needs to be placed long length = getLength(); - if (length > Integer.MAX_VALUE) { - // TODO: implement this case - throw new InvalidInputException("train length is bigger than 32-bit integer!"); - } - LinkedList positions = new LinkedList<>(); - positions.addLast(position); + final LinkedList positionList = new LinkedList<>(); + positionList.addLast(position); Vector2D rollingStockPosition = position; - if (length > 10000) { - return false; // TODO: remove! - } // check that the requested direction is equal to the direction of one the tracks if ((railNetwork.findContainingRail(position) != null && !railNetwork.findContainingRail(position).allowsDirectionFrom(position, positioningDirection)) @@ -162,37 +160,37 @@ public final class Train { .noneMatch(rail -> rail.allowsDirectionFrom(position, direction)))) { return false; } - if (railNetwork.findTouchingRails(position).length > 0 - && (Arrays.stream(railNetwork.findTouchingRails(position)) - .noneMatch(rail -> rail.connectsTo(position)))) { - return false; - } - // consider fuzz 9 (0,-1) - for (int i = 1; i <= length; i++) { - rollingStockPosition = railNetwork.move(rollingStockPosition, positioningDirection); - if (positions.getLast().distanceTo(rollingStockPosition) == 0) { + final Set occupiedRailsSet = new HashSet<>(); + while (length > 0) { + rollingStockPosition = railNetwork.move(rollingStockPosition, positioningDirection, length); + if (rollingStockPosition == null) { + // derailed + return false; + } + final long distanceToLastPosition = positionList.getLast().distanceTo(rollingStockPosition); + if (distanceToLastPosition == 0) { // stuck return false; } - if (rollingStockPosition == null - || positions.contains(rollingStockPosition) && !positions.get(0).equals(rollingStockPosition)) { - return false; + + // successfully placed part of the train + length -= distanceToLastPosition; + + final Rail occupiedRail = railNetwork.findRail(positionList.getLast(), rollingStockPosition).get(); + if (occupiedRailsSet.contains(occupiedRail) && positionList.size() >= 4) { + // perhaps a self intersection + final long occupiedAtFrontOfTrain = positionList.get(0).distanceTo(positionList.get(1)); + final long occupiedAtBackOfTrain = positionList.getLast().distanceTo(positionList.get(positionList.size() - 2)); + if (occupiedAtFrontOfTrain + occupiedAtBackOfTrain > occupiedRail.getLength()) { + // train definitely intersects itself + return false; + } } - positions.addLast(rollingStockPosition); + occupiedRailsSet.add(occupiedRail); + positionList.addLast(rollingStockPosition); } - Set occupiedRailsSet = positions.stream().flatMap(point -> { - Rail containingRail = railNetwork.findContainingRail(point); - if (containingRail != null) { - return Stream.of(containingRail); - } else { - return Arrays.stream(railNetwork.findTouchingRails(point)) - // ONLY add rails we fully occupy - .filter(rail -> positions.stream().filter(rail::connectsTo).count() == 2); - } - }).collect(Collectors.toSet()); - - this.position = positions; + this.positions = positionList; this.occupiedRails = occupiedRailsSet; return true; @@ -202,7 +200,7 @@ public final class Train { * @return whether this train is placed on a rail network */ public boolean isPlaced() { - return position != null && occupiedRails != null; + return positions != null && occupiedRails != null; } /** @@ -222,7 +220,7 @@ public final class Train { */ public Vector2D getFrontPosition() { // make sure caller can not modify internal data - return position != null ? new Vector2D(position.getFirst()) : null; + return positions != null ? new Vector2D(positions.getFirst()) : null; } /** @@ -230,22 +228,21 @@ public final class Train { */ public Vector2D getRearPosition() { // make sure caller can not modify internal data - return position != null ? new Vector2D(position.getLast()) : null; + return positions != null ? new Vector2D(positions.getLast()) : null; } /** * @return the direction of this train */ public Vector2D getDirection() { - // make sure caller can not modify internal state - return position.get(0).subtract(position.get(1)); + return positions.get(0).subtract(positions.get(1)).normalized(); } /** * @return the rear direction of this train */ public Vector2D getRearDirection() { - return position.getLast().subtract(position.get(position.size() - 2)); // TODO: consider longer trains + return positions.getLast().subtract(positions.get(positions.size() - 2)).normalized(); } /** @@ -263,15 +260,25 @@ public final class Train { * @param nextPosition position to move train to */ public void moveTo(RailwayNetwork railNetwork, Vector2D nextPosition) { - Vector2D last = position.removeLast(); + final Rail railUnderBackOfTrain = railNetwork.findContainingRail(positions.getLast()); + positions.getLast().subtractInPlace(getRearDirection()); + if (positions.getLast().equals(positions.get(positions.size() - 2))) { + if (railNetwork.findContainingRail(nextPosition) != railUnderBackOfTrain + || nextPosition == null) { + occupiedRails.remove(railUnderBackOfTrain); + } + positions.removeLast(); + } if (nextPosition != null) { // not derailing - position.addFirst(new Vector2D(nextPosition)); - updateOccupiedRails(railNetwork, position.getFirst(), last, position.getLast()); - } else { - // derailing - updateOccupiedRails(railNetwork, null, last, position.getLast()); - } + positions.addFirst(new Vector2D(nextPosition)); + if (positions.size() >= 3 && nextPosition.subtract(positions.get(1)).normalized() + .equals(positions.get(1).subtract(positions.get(2)).normalized()) + && railNetwork.findTouchingRails(positions.get(1)).length == 0) { + positions.remove(1); + } + occupiedRails.add(railNetwork.findRail(positions.get(1), nextPosition).get()); + } // else: derailing } /** @@ -279,66 +286,35 @@ public final class Train { * derailing. * * @param railNetwork rail network to move train on - * @param rearPosition position to move rear of the train to + * @param backPosition position to move back of the train to */ - public void moveBackTo(final RailwayNetwork railNetwork, final Vector2D rearPosition) { - Vector2D last = position.removeFirst(); - if (rearPosition != null) { + public void moveBackTo(RailwayNetwork railNetwork, Vector2D backPosition) { + final Rail railUnderFrontOfTrain = railNetwork.findContainingRail(positions.getFirst()); + positions.getFirst().subtractInPlace(getDirection()); + if (positions.getFirst().equals(positions.get(1))) { + if (railNetwork.findContainingRail(backPosition) != railUnderFrontOfTrain + || backPosition == null) { + occupiedRails.remove(railUnderFrontOfTrain); + } + positions.removeFirst(); + } + if (backPosition != null) { // not derailing - position.addLast(new Vector2D(rearPosition)); - updateOccupiedRails(railNetwork, position.getLast(), last, position.getFirst()); - } else { - // derailing - updateOccupiedRails(railNetwork, null, last, position.getFirst()); - } - } - - /** - * Update set of occupied rails after modifying position. - * - * @param railNetwork rail network to use - * @param nextPosition where the train just moved (null if derailing) - * @param lastPosition where the train just was - * @param secondToLastPosition where the end of the train is now - */ - private void updateOccupiedRails(final RailwayNetwork railNetwork, final Vector2D nextPosition, - final Vector2D lastPosition, final Vector2D secondToLastPosition) { - if (secondToLastPosition.equals(nextPosition)) { - // we are moving in a loop, no need to update - return; - } - // update occupied blocks, first removing the rail block left behind - Rail leavingRail = railNetwork.findContainingRail(lastPosition); - if (leavingRail != null && !leavingRail.contains(secondToLastPosition)) { - // we are leaving this rail - occupiedRails.remove(leavingRail); - } else if (getLength() == 1 && railNetwork.findContainingRail(secondToLastPosition) == null) { - // we evidently just moved into another rail - occupiedRails.clear(); - } // else: only touched rail - // update occupied rails, adding next rail - if (nextPosition == null) { - // derailing - return; - } - Rail nextRail = railNetwork.findContainingRail(nextPosition); - if (nextRail != null) { - occupiedRails.add(nextRail); - } else if (getLength() == 1 && railNetwork.findContainingRail(secondToLastPosition) == null) { - // we evidently just moved into another rail - Rail[] nextTouchingRails = railNetwork.findTouchingRails(nextPosition); - Rail[] alreadyTouchingRails = railNetwork.findTouchingRails(secondToLastPosition); - Arrays.stream(nextTouchingRails) - .filter(rail -> Arrays.stream(alreadyTouchingRails).anyMatch(rail2 -> rail == rail2)) - .forEach(occupiedRails::add); - } // else: only touching new rail + positions.addLast(new Vector2D(backPosition)); + if (positions.size() >= 3 && backPosition.subtract(positions.get(positions.size() - 2)).normalized() + .equals(positions.get(positions.size() - 2).subtract(positions.get(positions.size() - 3)).normalized()) + && railNetwork.findTouchingRails(positions.get(positions.size() - 2)).length == 0) { + positions.remove(positions.size() - 2); + } + occupiedRails.add(railNetwork.findRail(positions.get(positions.size() - 2), backPosition).get()); + } // else: derailing } /** * Remove this train from the rail network. */ public void removeFromRails() { - position = null; + positions = null; occupiedRails = null; } @@ -348,7 +324,7 @@ public final class Train { * @return whether this train touches the other train */ public boolean touches(final Train other) { - return position != null && other.isOnAnyPosition(position); + return positions != null && other.isOnAnyPosition(positions); } /** @@ -358,7 +334,7 @@ public final class Train { * @return whether this train is on the specified position */ public boolean isOnPosition(final Vector2D point) { - return position.contains(point); + return positions.contains(point); } /** @@ -368,7 +344,7 @@ public final class Train { * @return whether this train is on any of the specified positions */ public boolean isOnAnyPosition(List positions) { - return position != null && position.stream().anyMatch(positions::contains); + return this.positions != null && this.positions.stream().anyMatch(positions::contains); } /** diff --git a/src/edu/kit/informatik/model/TrainManager.java b/src/edu/kit/informatik/model/TrainManager.java index bb3cb9f..becf6d0 100644 --- a/src/edu/kit/informatik/model/TrainManager.java +++ b/src/edu/kit/informatik/model/TrainManager.java @@ -14,7 +14,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -273,9 +272,8 @@ public final class TrainManager { trains.values().stream().filter(Train::isPlaced).forEach(train -> { Vector2D position = train.getFrontPosition(); Vector2D direction = train.getDirection(); - Vector2D nextPosition = railNetwork.move(position, direction); - if (nextPosition == null || nextPosition.equals(position) - || train.isOnPosition(nextPosition) && !train.getRearPosition().equals(train.getFrontPosition())) { + Vector2D nextPosition = railNetwork.move(position, direction, 1); + if (nextPosition == null || nextPosition.equals(position)) { train.moveTo(railNetwork, null); collisions.add(new HashSet<>(Arrays.asList(train))); } else { @@ -295,10 +293,9 @@ public final class TrainManager { List> collisions = new ArrayList<>(); // perform step trains.values().stream().filter(Train::isPlaced).forEach(train -> { - Vector2D front = train.getFrontPosition(); Vector2D position = train.getRearPosition(); Vector2D direction = train.getRearDirection(); - Vector2D nextPosition = railNetwork.move(position, direction); + Vector2D nextPosition = railNetwork.move(position, direction, 1); if (nextPosition == null || train.isOnPosition(nextPosition) && !train.getRearPosition().equals(train.getFrontPosition())) { train.moveBackTo(railNetwork, nextPosition); diff --git a/src/edu/kit/informatik/model/Vector2D.java b/src/edu/kit/informatik/model/Vector2D.java index 33eae3e..ec6fdad 100644 --- a/src/edu/kit/informatik/model/Vector2D.java +++ b/src/edu/kit/informatik/model/Vector2D.java @@ -93,6 +93,17 @@ public class Vector2D { return new Vector2D(x - other.x, y - other.y); } + /** + * Subtract another vector from this one, modifying this vector. + * + * @param other another vector + * @throws NullPointerException if other is null + */ + public void subtractInPlace(Vector2D other) { + x -= other.x; + y -= other.y; + } + /** * @param movement vector to add * @return component-wise sum of this vector and the other vector