Day 16: Reindeer Maze
Megathread guidelines
- Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
- You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL
FAQ
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465
C#
using QuickGraph; using QuickGraph.Algorithms.ShortestPath; namespace aoc24; [ForDay(16)] public class Day16 : Solver { private string[] data; private int width, height; private int start_x, start_y; private int end_x, end_y; private readonly (int, int)[] directions = [(1, 0), (0, 1), (-1, 0), (0, -1)]; private record class Edge((int, int, int) Source, (int, int, int) Target) : IEdge<(int, int, int)>; private DelegateVertexAndEdgeListGraph<(int, int, int), Edge> graph; private AStarShortestPathAlgorithm<(int, int, int), Edge> search; private long min_distance; private List<(int, int, int)> min_distance_targets; public void Presolve(string input) { data = input.Trim().Split("\n"); width = data[0].Length; height = data.Length; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { if (data[j][i] == 'S') { start_x = i; start_y = j; } else if (data[j][i] == 'E') { end_x = i; end_y = j; } } } graph = MakeGraph(); var start = (start_x, start_y, 0); search = new AStarShortestPathAlgorithm<(int, int, int), Edge>( graph, edge => edge.Source.Item3 == edge.Target.Item3 ? 1 : 1000, vertex => Math.Abs(vertex.Item1 - start_x) + Math.Abs(vertex.Item2 - start_y) + 1000 * Math.Min(vertex.Item3, 4 - vertex.Item3) ); Dictionary<(int, int, int), long> distances = []; search.SetRootVertex(start); search.ExamineVertex += vertex => { if (vertex.Item1 == end_x && vertex.Item2 == end_y) { distances[vertex] = (long)search.Distances[vertex]; } }; search.Compute(); min_distance = distances.Values.Min(); min_distance_targets = distances.Keys.Where(v => distances[v] == min_distance).ToList(); } private DelegateVertexAndEdgeListGraph<(int, int, int), Edge> MakeGraph() => new(GetAllVertices(), GetOutEdges); private bool GetOutEdges((int, int, int) arg, out IEnumerable<Edge> result_enumerable) { List<Edge> result = []; var (x, y, dir) = arg; result.Add(new Edge(arg, (x, y, (dir + 1) % 4))); result.Add(new Edge(arg, (x, y, (dir + 3) % 4))); var (tx, ty) = (x + directions[dir].Item1, y + directions[dir].Item2); if (data[ty][tx] != '#') result.Add(new Edge(arg, (tx, ty, dir))); result_enumerable = result; return true; } private IEnumerable<(int, int, int)> GetAllVertices() { for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { if (data[j][i] == '#') continue; yield return (i, j, 0); yield return (i, j, 1); yield return (i, j, 2); yield return (i, j, 3); } } } private HashSet<(int, int, int)> GetMinimumPathNodesTo((int, int, int) vertex) { var (x, y, dir) = vertex; if (x == start_x && y == start_y && dir == 0) return [vertex]; if (!search.Distances.TryGetValue(vertex, out var distance_to_me)) return []; List<(int, int, int)> candidates = [ (x, y, (dir + 1) % 4), (x, y, (dir + 3) % 4), (x - directions[dir].Item1, y - directions[dir].Item2, dir), ]; HashSet<(int, int, int)> result = [vertex]; foreach (var (cx, cy, cdir) in candidates) { if (!search.Distances.TryGetValue((cx, cy, cdir), out var distance_to_candidate)) continue; if (distance_to_candidate > distance_to_me - (dir == cdir ? 1 : 1000)) continue; result = result.Union(GetMinimumPathNodesTo((cx, cy, cdir))).ToHashSet(); } return result; } public string SolveFirst() => min_distance.ToString(); public string SolveSecond() => min_distance_targets .SelectMany(v => GetMinimumPathNodesTo(v)) .Select(vertex => (vertex.Item1, vertex.Item2)) .ToHashSet() .Count .ToString(); }