-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
323 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package net.bqc.aoc.year2024; | ||
|
||
import net.bqc.aoc.Solution; | ||
import net.bqc.aoc.utils.Array2DUtils; | ||
import net.bqc.aoc.utils.MathUtils; | ||
import net.bqc.aoc.utils.Pos; | ||
|
||
import java.util.*; | ||
|
||
public class Day20 extends Solution<Long> { | ||
|
||
private enum Direction { | ||
NORTH("^", -1, 0), EAST(">", 0, 1), | ||
SOUTH("v", 1, 0), WEST("<", 0, -1); | ||
|
||
final String symbol; | ||
final int dx, dy; | ||
|
||
Direction(String symbol, int dx, int dy) { | ||
this.symbol = symbol; | ||
this.dx = dx; | ||
this.dy = dy; | ||
} | ||
|
||
public static Direction[] nextDirection(Direction d) { | ||
if (d == null) return new Direction[]{NORTH, EAST, SOUTH, WEST}; | ||
return switch (d) { | ||
case NORTH -> new Direction[]{NORTH, EAST, WEST}; | ||
case SOUTH -> new Direction[]{SOUTH, EAST, WEST}; | ||
case EAST -> new Direction[]{NORTH, SOUTH, EAST}; | ||
case WEST -> new Direction[]{NORTH, SOUTH, WEST}; | ||
}; | ||
} | ||
} | ||
|
||
private static class PathNode { | ||
Pos pos; | ||
long cost; | ||
Direction direction; | ||
PathNode prev; | ||
|
||
public PathNode(Pos pos, long cost, Direction direction) { | ||
this.pos = pos; | ||
this.cost = cost; | ||
this.direction = direction; | ||
} | ||
} | ||
|
||
@Override | ||
public Long solve(Part part, List<String> inputLines) { | ||
super.solve(part, inputLines); | ||
char[][] map = Array2DUtils.readAsMatrix(inputLines); | ||
return countCheats(map, isPart2() ? 20 : 2); | ||
} | ||
|
||
private long countCheats(char[][] map, int maxDist) { | ||
Pos startPos = Array2DUtils.getXPos(map, 'S').get(0); | ||
Pos endPos = Array2DUtils.getXPos(map, 'E').get(0); | ||
|
||
List<PathNode> initialPath = shortestPath(map, startPos, endPos); | ||
long initialBestCost = initialPath.get(initialPath.size() - 1).cost; | ||
long cheatCount = 0; | ||
|
||
for (int i = 0; i < initialPath.size() - 1; i++) { | ||
PathNode pi = initialPath.get(i); | ||
for (int j = i + 1; j < initialPath.size(); j++) { | ||
PathNode pj = initialPath.get(j); | ||
int dist = MathUtils.manhattanDistance(pi.pos.x, pi.pos.y, pj.pos.x, pj.pos.y); | ||
if (dist <= maxDist && (j - i - dist) >= 100) cheatCount++; | ||
} | ||
} | ||
|
||
return cheatCount; | ||
} | ||
|
||
private List<PathNode> shortestPath(char[][] map, Pos startPos, Pos endPos) { | ||
PathNode[][] costMap = new PathNode[map.length][map[0].length]; | ||
costMap[startPos.x][startPos.y] = new PathNode(startPos, 0, Direction.EAST); | ||
|
||
PriorityQueue<PathNode> pq = new PriorityQueue<>((o1, o2) -> (int) (o2.cost - o1.cost)); | ||
pq.add(costMap[startPos.x][startPos.y]); | ||
while (!pq.isEmpty()) { | ||
PathNode curr = pq.poll(); | ||
|
||
for (Direction d : Direction.nextDirection(curr.direction)) { | ||
int newX = curr.pos.x + d.dx; | ||
int newY = curr.pos.y + d.dy; | ||
long nextCost = curr.cost + 1; | ||
|
||
if (map[newX][newY] == '#') continue; | ||
|
||
if (costMap[newX][newY] == null) { | ||
costMap[newX][newY] = new PathNode(new Pos(newX, newY), Long.MAX_VALUE, d); | ||
} | ||
|
||
if (nextCost < costMap[newX][newY].cost) { | ||
costMap[newX][newY].cost = nextCost; | ||
costMap[newX][newY].prev = curr; | ||
pq.add(costMap[newX][newY]); | ||
} | ||
} | ||
} | ||
|
||
List<PathNode> path = new ArrayList<>(); | ||
PathNode curr = costMap[endPos.x][endPos.y]; | ||
while (curr != null) { | ||
path.add(curr); | ||
curr = curr.prev; | ||
} | ||
Collections.reverse(path); | ||
return path; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package net.bqc.aoc.year2024; | ||
|
||
import net.bqc.aoc.AbstractTest; | ||
import net.bqc.aoc.Solution; | ||
import org.junit.jupiter.api.BeforeAll; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.Arguments; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
|
||
import java.util.List; | ||
import java.util.stream.Stream; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
||
public class Day20Test extends AbstractTest { | ||
|
||
private Day20 solution; | ||
|
||
@BeforeAll | ||
static void setUp() { | ||
INPUT_PATH = "year2024/Day20_Input.txt"; | ||
SAMPLE_INPUT_PATH = "year2024/Day20_SampleInput.txt"; | ||
} | ||
|
||
@BeforeEach | ||
void init() { | ||
solution = new Day20(); | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource("inputDataSource") | ||
void testSolver(Solution.Part part, List<String> inputLines, long expectedOutput) { | ||
long computedOutput = solution.solve(part, inputLines); | ||
assertEquals(expectedOutput, computedOutput); | ||
} | ||
|
||
static Stream<Arguments> inputDataSource() { | ||
List<String> sampleInputLines = getSampleInput(); | ||
List<String> inputLines = getInput(); | ||
return Stream.of( | ||
Arguments.of(Solution.Part.ONE, sampleInputLines, 0), | ||
Arguments.of(Solution.Part.ONE, inputLines, 1289), | ||
|
||
Arguments.of(Solution.Part.TWO, sampleInputLines, 0), | ||
Arguments.of(Solution.Part.TWO, inputLines, 982425) | ||
); | ||
} | ||
} |
Oops, something went wrong.