Skip to content

Commit

Permalink
Day 21
Browse files Browse the repository at this point in the history
  • Loading branch information
bqcuong committed Dec 22, 2024
1 parent fe732e4 commit ff26d3b
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 22 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ This repository contains my Java solutions for the [Advent of Code](https://adve
| 18 | [RAM Run](https://adventofcode.com/2024/day/18) | ⭐⭐ | [Code](src/main/java/net/bqc/aoc/year2024/Day18.java), [Test](src/test/java/net/bqc/aoc/year2024/Day18Test.java) |
| 19 | [Linen Layout](https://adventofcode.com/2024/day/19) | ⭐⭐ | [Code](src/main/java/net/bqc/aoc/year2024/Day19.java), [Test](src/test/java/net/bqc/aoc/year2024/Day19Test.java) |
| 20 | [Race Condition](https://adventofcode.com/2024/day/20) | ⭐⭐ | [Code](src/main/java/net/bqc/aoc/year2024/Day20.java), [Test](src/test/java/net/bqc/aoc/year2024/Day20Test.java) |
| 21 | [](https://adventofcode.com/2024/day/21) | | [Code](src/main/java/net/bqc/aoc/year2024/Day21.java), [Test](src/test/java/net/bqc/aoc/year2024/Day21Test.java) |
| 21 | [Keypad Conundrum](https://adventofcode.com/2024/day/21) | ⭐⭐ | [Code](src/main/java/net/bqc/aoc/year2024/Day21.java), [Test](src/test/java/net/bqc/aoc/year2024/Day21Test.java) |
| 22 | [](https://adventofcode.com/2024/day/22) | | [Code](src/main/java/net/bqc/aoc/year2024/Day22.java), [Test](src/test/java/net/bqc/aoc/year2024/Day22Test.java) |
| 23 | [](https://adventofcode.com/2024/day/23) | | [Code](src/main/java/net/bqc/aoc/year2024/Day23.java), [Test](src/test/java/net/bqc/aoc/year2024/Day23Test.java) |
| 24 | [](https://adventofcode.com/2024/day/24) | | [Code](src/main/java/net/bqc/aoc/year2024/Day24.java), [Test](src/test/java/net/bqc/aoc/year2024/Day24Test.java) |
Expand Down
63 changes: 43 additions & 20 deletions src/main/java/net/bqc/aoc/utils/GraphUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,8 @@ public static enum CDirection {
this.dy = dy;
}

public static CDirection[] nextDirection(CDirection d) {
if (d == null) return new CDirection[]{NORTH, EAST, SOUTH, WEST};
return switch (d) {
case NORTH -> new CDirection[]{NORTH, EAST, WEST};
case SOUTH -> new CDirection[]{SOUTH, EAST, WEST};
case EAST -> new CDirection[]{NORTH, SOUTH, EAST};
case WEST -> new CDirection[]{NORTH, SOUTH, WEST};
};
public CDirection[] nextDirection() {
return new CDirection[]{ EAST, WEST, SOUTH, NORTH };
}
}

Expand All @@ -41,44 +35,73 @@ public PathNode(Pos pos, long cost, CDirection direction) {
this.cost = cost;
this.direction = direction;
}

@Override
public String toString() {
return String.format("%s (%d, %d)", direction.symbol, pos.x, pos.y);
}
}

public static List<List<PathNode>> shortestPaths(char[][] map, char startChar, char endChar, char blockingTile) {
Pos startPos = Array2DUtils.getXPos(map, startChar).get(0);
Pos endPos = Array2DUtils.getXPos(map, endChar).get(0);
return shortestPaths(map, startPos, endPos, blockingTile);
}

public static List<PathNode> shortestPath(char[][] map, Pos startPos, Pos endPos, char blockingTile) {
// Implements Dijkstra's shortest path algorithm
public static List<List<PathNode>> shortestPaths(char[][] map, Pos startPos, Pos endPos, char blockingTile) {
// Implements Dijkstra's algorithm to find all shortest paths
PathNode[][] costMap = new PathNode[map.length][map[0].length];
costMap[startPos.x][startPos.y] = new PathNode(startPos, 0, CDirection.EAST);

PriorityQueue<PathNode> pq = new PriorityQueue<>((o1, o2) -> (int) (o2.cost - o1.cost));
pq.add(costMap[startPos.x][startPos.y]);

List<List<PathNode>> paths = new ArrayList<>();
long bestCost = Long.MAX_VALUE;

while (!pq.isEmpty()) {
PathNode curr = pq.poll();

for (CDirection d : CDirection.nextDirection(curr.direction)) {
if (curr.pos.x == endPos.x && curr.pos.y == endPos.y) {
if (curr.cost < bestCost) {
bestCost = curr.cost;
paths.clear();
}
if (curr.cost == bestCost) {
List<PathNode> path = new ArrayList<>();
PathNode c = costMap[endPos.x][endPos.y];
while (c != null) {
PathNode clone = new PathNode(c.pos, c.cost, c.direction);
path.add(clone);
c = c.prev;
}
Collections.reverse(path);
paths.add(path);
}
continue;
}

for (CDirection d : curr.direction.nextDirection()) {
int newX = curr.pos.x + d.dx;
int newY = curr.pos.y + d.dy;
long nextCost = curr.cost + 1;

if (newX < 0 || newX >= map.length || newY < 0 || newY >= map[0].length) continue;
if (map[newX][newY] == blockingTile) 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) {
if (nextCost <= costMap[newX][newY].cost) {
costMap[newX][newY].cost = nextCost;
costMap[newX][newY].prev = curr;
costMap[newX][newY].direction = d;
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;
return paths;
}
}
2 changes: 1 addition & 1 deletion src/main/java/net/bqc/aoc/year2024/Day20.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ 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, '#');
List<PathNode> initialPath = shortestPaths(map, startPos, endPos, '#').get(0);
long cheatCount = 0;

for (int i = 0; i < initialPath.size() - 1; i++) {
Expand Down
65 changes: 65 additions & 0 deletions src/main/java/net/bqc/aoc/year2024/Day21.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package net.bqc.aoc.year2024;

import net.bqc.aoc.Solution;

import net.bqc.aoc.utils.GraphUtils;

import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

public class Day21 extends Solution<Long> {

private final static char[][] NUMPAD = new char[][]{
{'7', '8', '9'},
{'4', '5', '6'},
{'1', '2', '3'},
{' ', '0', 'A'}
};

private final static char[][] DPAD = new char[][]{
{' ', '^', 'A'},
{'<', 'v', '>'}
};

// Saving <String, String> occupies much more space and might cause heap out of memory
private final static HashMap<String, Long> memorization = new HashMap<>();

@Override
public Long solve(Part part, List<String> inputLines) {
super.solve(part, inputLines);
return inputLines.stream()
.map(l -> Long.parseLong(l.substring(0, l.length() - 1)) * getMinCodeLength(NUMPAD, l, isPart2() ? 26 : 3))
.reduce(0L, Long::sum);
}

private long getMinCodeLength(char[][] pad, String code, int depth) {
String key = code + "-" + depth;
if (memorization.containsKey(key)) return memorization.get(key);

long length = 0L;
char startCh = 'A';
for (char codeCh : code.toCharArray()) {
List<String> paths = shortestPaths(pad, startCh, codeCh);
long minLength = Long.MAX_VALUE;
if (depth == 1) {
minLength = paths.get(0).length();
}
else {
for (String path : paths) {
minLength = Math.min(minLength, getMinCodeLength(DPAD, path, depth - 1));
}
}
length += minLength;
startCh = codeCh;
}
memorization.put(key, length);
return length;
}

private List<String> shortestPaths(char[][] pad, char start, char end) {
return GraphUtils.shortestPaths(pad, start, end, ' ').stream()
.map(p -> p.stream().map(n -> n.direction.symbol).skip(1).collect(Collectors.joining("")) + "A")
.collect(Collectors.toList());
}
}
49 changes: 49 additions & 0 deletions src/test/java/net/bqc/aoc/year2024/Day21Test.java
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 Day21Test extends AbstractTest {

private Day21 solution;

@BeforeAll
static void setUp() {
INPUT_PATH = "year2024/Day21_Input.txt";
SAMPLE_INPUT_PATH = "year2024/Day21_SampleInput.txt";
}

@BeforeEach
void init() {
solution = new Day21();
}

@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, 126384),
Arguments.of(Solution.Part.ONE, inputLines, 138764),

Arguments.of(Solution.Part.TWO, sampleInputLines, 154115708116294L),
Arguments.of(Solution.Part.TWO, inputLines, 169137886514152L)
);
}
}
5 changes: 5 additions & 0 deletions src/test/resources/year2024/Day21_Input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
140A
180A
176A
805A
638A
5 changes: 5 additions & 0 deletions src/test/resources/year2024/Day21_SampleInput.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
029A
980A
179A
456A
379A

0 comments on commit ff26d3b

Please sign in to comment.