Advent of Code - Day 18: RAM Run


2024-12-18-generated.png

You and The Historians look a lot more pixelated than you remember. You’re inside a computer at the North Pole!

-- Day 18 - Advent of Code 2024

Solution in Java

Full source can be found: in GitHub

Today the assignment involves yet another grid and pathfinding algorithm. So to read the input into memory I created this small method.

1public void readInput() {  
2    grid = new CharGrid(71, 71);
3    inputLoader.splitOnNewLine()  
4            .map(Point::of)  
5            .limit(1024)                        // limit of bytes to fall part1  
6            .forEach(point -> grid.set(point, '#'));  
7}

Part 1

Since the best solution is using the Dijkstra pathfinding algorithm I created a HistorianStep implementation of the Node<T> that is required by the algorithm.

This implementation uses a cache to speed up computation for any already visited node in the grid.

 1private class HistorianStep extends Node<HistorianStep> {  
 2    private final Point location;  
 3    private final Map<Point, HistorianStep> cache;  
 4  
 5    public HistorianStep(Point location, Map<Point, HistorianStep> cache) {  
 6        this.location = location;  
 7        this.cache = cache;  
 8    }  
 9  
10    @Override  
11    public List<NeighbourWithCost<HistorianStep>> neighbours() {  
12        return location.neighbours()  
13                .stream()  
14                .filter(point -> grid.at(point) == '.')  
15                .map(point -> cache.computeIfAbsent(point, key -> new HistorianStep(key, cache)))  
16                .map(step -> new NeighbourWithCost<>(step, 1L))  
17                .toList();  
18    }  
19}

Using the HistorianStep and the already processed input the remainder for part 1 is just a matter of preparing the Dijkstra and cache.

 1public void part1() {  
 2    var stepCache = new HashMap<Point, HistorianStep>();  
 3    var start = new HistorianStep(Point.zero, stepCache);  
 4    var end = Point.of(grid.cols() - 1, grid.rows() - 1);  
 5  
 6    start.setCost(0L);  
 7    var cheapestRoute = PathFinding.dijkstra(List.of(start), step -> step.location.equals(end));  
 8    if (cheapestRoute.isEmpty()) {  
 9        throw new IllegalStateException("No path found.");  
10    }  
11  
12    validator.part1(cheapestRoute.get().cost());  
13}

Part 2

For the second part for today I chose a very naïve but simple approach. By creating a list of all not yet added blocks I could remove the first in a loop. Adding the removed block to the grid and re-running the pathfinding.

Then it is only a matter of repeating this exercise of removing the blockade and adding to the grid until the pathfinding no longer finds any routes.

 1public void part2() {  
 2    var start = new HistorianStep(Point.zero, new HashMap<>());  
 3    var end = Point.of(grid.cols() - 1, grid.rows() - 1);  
 4  
 5    var allBlocks = new ArrayList<>(inputLoader.splitOnNewLine()  
 6            .map(Point::of)  
 7            .skip(1024)  
 8            .toList());  
 9  
10    Point lastBlockAdded = null;  
11    start.setCost(0L);  
12    while (PathFinding.dijkstra(List.of(start), step -> step.location.equals(end)).isPresent()) {  
13        lastBlockAdded = allBlocks.removeFirst();  
14        grid.set(lastBlockAdded, '#');  
15  
16        start = new HistorianStep(Point.zero, new HashMap<>());  
17        start.setCost(0L);  
18    }  
19  
20    validator.part2(lastBlockAdded.x() + "," + lastBlockAdded.y());  
21}

See also