Optimize large train placement

This commit is contained in:
Arne Keller 2020-03-04 22:22:05 +01:00
parent 70a070d9b1
commit 4fad0a377e
12 changed files with 184 additions and 150 deletions

View File

@ -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

View File

@ -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

13
long_train_input.txt Normal file
View File

@ -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

13
long_train_output.txt Normal file
View File

@ -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

View File

@ -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();

View File

@ -87,11 +87,20 @@ public abstract class Rail {
public abstract boolean allowsDirectionFrom(Vector2D position, Vector2D direction);
/**
* 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
* @param direction
* @param steps
* @return
* @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();
}

View File

@ -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<Rail> 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

View File

@ -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());
}
}

View File

@ -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);
}

View File

@ -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<RollingStock> 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<Vector2D> position;
private LinkedList<Vector2D> 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<Vector2D> positions = new LinkedList<>();
positions.addLast(position);
final LinkedList<Vector2D> 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)))) {
final Set<Rail> occupiedRailsSet = new HashSet<>();
while (length > 0) {
rollingStockPosition = railNetwork.move(rollingStockPosition, positioningDirection, length);
if (rollingStockPosition == null) {
// derailed
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 long distanceToLastPosition = positionList.getLast().distanceTo(rollingStockPosition);
if (distanceToLastPosition == 0) {
// stuck
return false;
}
if (rollingStockPosition == null
|| positions.contains(rollingStockPosition) && !positions.get(0).equals(rollingStockPosition)) {
// 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<Rail> 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());
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);
}
}
/**
* 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
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<Vector2D> positions) {
return position != null && position.stream().anyMatch(positions::contains);
return this.positions != null && this.positions.stream().anyMatch(positions::contains);
}
/**

View File

@ -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<Set<Train>> 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);

View File

@ -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