Skip to content

Commit 4fa8108

Browse files
committed
Day 20: Race Condition
1 parent 26f5de0 commit 4fa8108

File tree

5 files changed

+176
-68
lines changed

5 files changed

+176
-68
lines changed
Lines changed: 36 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,65 @@
11
package pl.apostaremczak.aoc;
22

3+
import pl.apostaremczak.aoc.util.CharacterMap2d;
34
import pl.apostaremczak.aoc.util.Coord2D;
4-
import pl.apostaremczak.aoc.util.Map2D;
55

66
import java.util.*;
77

88
public class Day20 extends PuzzleSolution {
99
Coord2D StartPosition;
1010
Coord2D EndPosition;
11-
Map<CheatPosition, Long> CheatedPathLengths = new HashMap<>();
1211
Long ShortestLegalPath = Long.MAX_VALUE;
13-
// Set<Coord2D> Walls;
14-
Map2D<Character> Maze;
12+
CharacterMap2d Maze;
1513
Long SavedTimeThreshold;
16-
Set<CheatPosition> ExploredCheats = new HashSet<>();
14+
Map<Coord2D, Long> DistancesFromStart;
15+
Map<Coord2D, Long> DistancesFromEnd;
16+
1717

1818
public Day20(String inputFilename, Long savedTimeThreshold) {
1919
super(inputFilename);
20-
Maze = Map2D.fromStringInputLines(inputLines);
20+
Maze = CharacterMap2d.fromStringInputLines(inputLines, '#');
2121
StartPosition = Maze.findFirst('S').get();
2222
EndPosition = Maze.findFirst('E').get();
2323
SavedTimeThreshold = savedTimeThreshold;
24-
}
25-
26-
@Override
27-
public Long solvePart1() {
28-
findRaceTrackLength(new LinkedList<>(), StartPosition, Optional.empty());
29-
// How many cheats would save you at least 100 picoseconds?
30-
long result = 0;
31-
for (long cheatedPathLength : CheatedPathLengths.values()) {
32-
if (ShortestLegalPath - cheatedPathLength >= SavedTimeThreshold) {
33-
result++;
34-
}
35-
}
36-
return result;
37-
}
3824

39-
@Override
40-
public Long solvePart2() {
41-
return 0L;
25+
DistancesFromStart = Maze.getDistancesFrom(StartPosition);
26+
DistancesFromEnd = Maze.getDistancesFrom(EndPosition);
27+
ShortestLegalPath = DistancesFromStart.get(EndPosition);
4228
}
4329

44-
private Boolean isWall(Coord2D position) {
45-
return Maze.safeGetAt(position).orElse('X').equals('#');
46-
}
47-
48-
// DFS for finding track lengths
49-
private void findRaceTrackLength(LinkedList<Coord2D> visited, Coord2D currentNode, Optional<CheatPosition> cheatPosition) {
50-
visited.add(currentNode);
51-
52-
if (currentNode.equals(EndPosition)) {
53-
long currentPathLength = visited.size();
54-
if (cheatPosition.isEmpty()) {
55-
if (currentPathLength < ShortestLegalPath) {
56-
ShortestLegalPath = currentPathLength;
57-
}
58-
} else {
59-
CheatedPathLengths.put(cheatPosition.get(), currentPathLength);
60-
}
61-
}
30+
private Long countShortcuts(Integer maxShortcutLength) {
31+
long shorterPathCount = 0L;
6232

63-
for (Coord2D neighbor : currentNode.getStraightSurrounding()) {
64-
if (!visited.contains(neighbor) && Maze.isWithinBounds(neighbor)) {
65-
// Try cheating if it hasn't been done already and if the next tile after the wall is not a wall itself
66-
if (isWall(neighbor) && cheatPosition.isEmpty()) {
67-
Coord2D direction = neighbor.minus(currentNode);
68-
Coord2D afterWallNode = neighbor.add(direction);
69-
if (Maze.isWithinBounds(afterWallNode) && !isWall(afterWallNode) && !visited.contains(afterWallNode)) {
70-
LinkedList<Coord2D> cheatVisited = new LinkedList<>(visited);
71-
cheatVisited.add(neighbor);
72-
CheatPosition cheat = new CheatPosition(neighbor, afterWallNode);
73-
if (!ExploredCheats.contains(cheat)) {
74-
ExploredCheats.add(cheat);
75-
findRaceTrackLength(cheatVisited, afterWallNode, Optional.of(cheat));
33+
// Iterate over all non-wall points on the grid
34+
for (int rowIdx = 0; rowIdx <= Maze.MAX_ROW_INDEX; rowIdx++) {
35+
for (int colIdx = 0; colIdx <= Maze.MAX_COLUMN_INDEX; colIdx++) {
36+
Coord2D currentPosition = new Coord2D(rowIdx, colIdx);
37+
if (!Maze.isWall(currentPosition)) {
38+
// Check all the neighbors and check if the tile after the neighbor is free
39+
for (Coord2D teleport : Maze.getManhattanClosedBallPoints(currentPosition, maxShortcutLength)) {
40+
if (!Maze.isWall(teleport)) {
41+
// Teleporting from p to q through a cheat
42+
// d(S, p) + d(p, q) + d(q, E)
43+
long cheatedDistance = DistancesFromStart.get(currentPosition) + currentPosition.manhattanDistanceFrom(teleport) + DistancesFromEnd.get(teleport);
44+
if (ShortestLegalPath - cheatedDistance >= SavedTimeThreshold) {
45+
shorterPathCount++;
46+
}
7647
}
7748
}
7849
}
79-
// Turn and proceed normally
80-
if (!isWall(neighbor)) {
81-
findRaceTrackLength(new LinkedList<>(visited), neighbor, cheatPosition);
82-
}
8350
}
8451
}
52+
return shorterPathCount;
53+
}
54+
55+
@Override
56+
public Long solvePart1() {
57+
return countShortcuts(2);
58+
}
59+
60+
@Override
61+
public Long solvePart2() {
62+
return countShortcuts(20);
8563
}
8664

8765
public static void main(String[] args) {
@@ -93,7 +71,3 @@ public static void main(String[] args) {
9371
}
9472
}
9573

96-
record CheatPosition(Coord2D first, Coord2D second) {
97-
98-
}
99-
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package pl.apostaremczak.aoc.util;
2+
3+
import java.util.*;
4+
5+
public class CharacterMap2d extends Map2D<Character> {
6+
Character WallSymbol;
7+
8+
public CharacterMap2d(Character[][] lines, Character wallSymbol) {
9+
super(lines);
10+
WallSymbol = wallSymbol;
11+
}
12+
13+
public static CharacterMap2d fromStringInputLines(String[] inputLines, Character wallSymbol) {
14+
return new CharacterMap2d(Arrays.stream(inputLines)
15+
.map(line -> line.chars().mapToObj(c -> (char) c).toArray(Character[]::new))
16+
.toArray(Character[][]::new), wallSymbol);
17+
}
18+
19+
public Boolean isWall(Coord2D position) {
20+
return this.safeGetAt(position).orElse('£').equals(WallSymbol);
21+
}
22+
23+
/**
24+
* BFS to find maze distances between a start point and all the other points.
25+
*
26+
* @param startPoint From where should the distances be calculated?
27+
* @return Map: { point -> distance from start to that point }
28+
*/
29+
public Map<Coord2D, Long> getDistancesFrom(Coord2D startPoint) {
30+
ArrayDeque<Coord2D> queue = new ArrayDeque<>();
31+
Map<Coord2D, Long> distancesFromStart = new HashMap<>();
32+
33+
queue.offer(startPoint);
34+
distancesFromStart.put(startPoint, 0L);
35+
36+
while (!queue.isEmpty()) {
37+
Coord2D current = queue.remove();
38+
Long currentDistance = distancesFromStart.get(current);
39+
for (Coord2D neighbor : current.getStraightSurrounding()) {
40+
// Hasn't been visited yet, is still on the map and isn't a wall
41+
if (!distancesFromStart.containsKey(neighbor) && isWithinBounds(neighbor) && !safeGetAt(neighbor).orElse('_').equals(WallSymbol)) {
42+
distancesFromStart.put(neighbor, currentDistance + 1);
43+
queue.offer(neighbor);
44+
}
45+
}
46+
}
47+
return distancesFromStart;
48+
}
49+
}

2024-java/src/main/java/pl/apostaremczak/aoc/util/Coord2D.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ public Coord2D rotateRightAngle(double angle) {
6060
return new Coord2D((int) newX, (int) newY);
6161
}
6262

63-
public Double euclideanDistance(Coord2D other) {
63+
public Double euclideanDistanceFrom(Coord2D other) {
6464
return Math.sqrt(Math.pow(this.row - other.row, 2) + Math.pow(this.column - other.column, 2));
6565
}
66+
67+
public Integer manhattanDistanceFrom(Coord2D other) {
68+
return Math.abs(this.row - other.row) + Math.abs(this.column - other.column);
69+
}
6670
}

2024-java/src/main/java/pl/apostaremczak/aoc/util/Map2D.java

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package pl.apostaremczak.aoc.util;
22

3-
import java.util.Arrays;
4-
import java.util.Optional;
3+
import java.util.*;
54

65
public class Map2D<T> {
76
public final T[][] internal;
@@ -26,17 +25,97 @@ public Optional<T> safeGetAt(Coord2D coord) {
2625
}
2726

2827
public T getAt(Coord2D coord) {
29-
assert isWithinBounds(coord): "Requested a field of out the map's boundaries";
28+
assert isWithinBounds(coord) : "Requested a field of out the map's boundaries";
3029
return internal[coord.row()][coord.column()];
3130
}
3231

3332
public boolean isWithinBounds(Coord2D coord) {
3433
return coord.row() >= 0 && coord.row() <= MAX_ROW_INDEX && coord.column() >= 0 && coord.column() <= MAX_COLUMN_INDEX;
3534
}
3635

36+
public Optional<Coord2D> findFirst(T element) {
37+
for (int rowIdx = 0; rowIdx <= MAX_ROW_INDEX; rowIdx++) {
38+
for (int colIdx = 0; colIdx <= MAX_COLUMN_INDEX; colIdx++) {
39+
Coord2D currentPosition = new Coord2D(rowIdx, colIdx);
40+
if (getAt(currentPosition).equals(element)) {
41+
return Optional.of(currentPosition);
42+
}
43+
}
44+
}
45+
return Optional.empty();
46+
}
47+
48+
public Set<Coord2D> findAll(T element) {
49+
Set<Coord2D> occurrences = new HashSet<>();
50+
for (int rowIdx = 0; rowIdx <= MAX_ROW_INDEX; rowIdx++) {
51+
for (int colIdx = 0; colIdx <= MAX_COLUMN_INDEX; colIdx++) {
52+
Coord2D currentPosition = new Coord2D(rowIdx, colIdx);
53+
if (getAt(currentPosition).equals(element)) {
54+
occurrences.add(currentPosition);
55+
}
56+
}
57+
}
58+
return occurrences;
59+
}
60+
3761
public static Map2D<Character> fromStringInputLines(String[] inputLines) {
3862
return new Map2D<>(Arrays.stream(inputLines)
3963
.map(line -> line.chars().mapToObj(c -> (char) c).toArray(Character[]::new))
4064
.toArray(Character[][]::new));
4165
}
66+
67+
public Iterator<Coord2D> positionIterator() {
68+
return new Iterator<Coord2D>() {
69+
private int currentRow = 0;
70+
private int currentCol = 0;
71+
72+
@Override
73+
public boolean hasNext() {
74+
return currentRow <= MAX_ROW_INDEX && currentCol <= MAX_COLUMN_INDEX;
75+
}
76+
77+
@Override
78+
public Coord2D next() {
79+
if (!hasNext()) {
80+
throw new NoSuchElementException();
81+
}
82+
Coord2D current = new Coord2D(currentRow, currentCol);
83+
if (currentCol < MAX_COLUMN_INDEX) {
84+
currentCol++;
85+
} else {
86+
currentCol = 0;
87+
currentRow++;
88+
}
89+
return current;
90+
}
91+
};
92+
}
93+
94+
/**
95+
* All points in the grid that are at == radius distance from the center, calculated with the Manhattan metric.
96+
*/
97+
public Set<Coord2D> getManhattanDiscPoints(Coord2D center, Integer radius) {
98+
Set<Coord2D> discElements = new HashSet<>();
99+
for (Iterator<Coord2D> it = this.positionIterator(); it.hasNext(); ) {
100+
Coord2D gridPoint = it.next();
101+
if (!gridPoint.equals(center) && center.manhattanDistanceFrom(gridPoint).equals(radius)) {
102+
discElements.add(gridPoint);
103+
}
104+
}
105+
return discElements;
106+
}
107+
108+
/**
109+
* All points in the grid that are at <= radius distance from the center, calculated with the Manhattan metric.
110+
*/
111+
public Set<Coord2D> getManhattanClosedBallPoints(Coord2D center, Integer radius) {
112+
Set<Coord2D> discElements = new HashSet<>();
113+
for (Iterator<Coord2D> it = this.positionIterator(); it.hasNext(); ) {
114+
Coord2D gridPoint = it.next();
115+
if (!gridPoint.equals(center) && center.manhattanDistanceFrom(gridPoint) <= radius) {
116+
discElements.add(gridPoint);
117+
}
118+
}
119+
return discElements;
120+
}
42121
}

2024-java/src/test/java/pl/apostaremczak/aoc/Day20Tests.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ public void testSolvePart1() {
1515

1616
@Test
1717
public void testSolvePart2() {
18-
Long result = day20.solvePart2();
19-
assertEquals(0L, result);
18+
final Day20 day = new Day20("src/test/resources/20.txt", 50L);
19+
20+
Long result = day.solvePart2();
21+
assertEquals(285L, result);
2022
}
2123
}

0 commit comments

Comments
 (0)