Skip to content

Commit fb89957

Browse files
committed
2024/16 make it more disktra like
1 parent c4aa58a commit fb89957

File tree

2 files changed

+27
-38
lines changed

2 files changed

+27
-38
lines changed

2024/Day16/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ You and The Historians arrive to search for the Chief right as the event is abou
55

66
_Visit the website for the full story and [full puzzle](https://adventofcode.com/2024/day/16) description._
77

8-
I spent hell a lot of time on this one. I’m not sure why, because I had a good understanding of what to do for both parts. `Part 1` went reasonably well: I quickly implemented a priority queue-based approach to find the shortest path from the `start` state to the `goal`.
8+
I spent hell a lot of time on this one. I’m not sure why, because I had a good understanding of what to do for both parts. `Part 1` went reasonably well: I quickly used a priority based approach to find the shortest path from the `start` state to the `goal`.
99

10-
For `Part 2`, I initially tried a few dead ends, but I found the right direction after about half an hour. The idea is to split the problem into two halves. First, we compute the optimal distances from every tile and direction to the goal node. This can be found in the `DistancesTo` function below. It is similar to how I implemented Part 1, using a priority queue, but I needed to walk backwards instead of forward.
10+
For `Part 2`, I initially tried a few dead ends, because I overcomplicate things as usual. But I found the right direction after about half an hour. The idea is to split the problem into two halves. First, we compute the optimal distances from every tile and direction to the goal node. This can be found using _Dijskstra's algorithm_.
1111

12-
Once I have the distances, I can start an other round, now working forward from the start position and using a flood-fill-like algorithm to discover the optimal positions. This is easy to do with the distance map as a guide. I can maintain the remaining score along the path, and I just need to check if the distance from a potential next state equals to the score I still have to use. This logic can be found in the `FindBestSpots` function.
12+
Once I have the distances, I can start an other round, now working forward from the start position and using a flood-fill-like algorithm to discover the positions on the shortest path. This is easy to do with the distance map as a guide. I maintain the 'remaining score' along the path, and just need to check if the distance from a potential next state equals to the score I still have to use. This logic can be found in the `FindBestSpots` function.

2024/Day16/Solution.cs

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
namespace AdventOfCode.Y2024.Day16;
22

3+
using System;
4+
using System.Collections;
35
using System.Collections.Generic;
46
using System.Collections.Immutable;
57
using System.Linq;
68
using System.Numerics;
9+
using AngleSharp.Common;
710
using Map = System.Collections.Generic.Dictionary<System.Numerics.Complex, char>;
811
using State = (System.Numerics.Complex pos, System.Numerics.Complex dir);
912

@@ -16,70 +19,56 @@ class Solution : Solver {
1619
static readonly Complex East = 1;
1720
static readonly Complex[] Dirs = { North, East, West, South };
1821

19-
public object PartOne(string input) => FindDistance(GetMap(input));
22+
public object PartOne(string input) => FindBestScore(GetMap(input));
2023
public object PartTwo(string input) => FindBestSpots(GetMap(input));
2124

22-
int FindDistance(Map map) {
23-
var dist = DistancesTo(map, Goal(map));
24-
return dist[Start(map)];
25-
}
25+
int FindBestScore(Map map) => Dijkstra(map, Goal(map))[Start(map)];
2626

27-
// determines the number tiles that are on one of the shortest paths in the race.
2827
int FindBestSpots(Map map) {
29-
var dist = DistancesTo(map, Goal(map));
28+
var dist = Dijkstra(map, Goal(map));
3029
var start = Start(map);
3130

32-
// flood fill algorithm determines the best spots by following the shortest paths
33-
// using the distance map as guideline.
34-
31+
// track the shortest paths using the distance map as guideline.
3532
var q = new PriorityQueue<State, int>();
3633
q.Enqueue(start, dist[start]);
37-
var bestSpots = new HashSet<State> { start };
3834

35+
var bestSpots = new HashSet<State> { start };
3936
while (q.TryDequeue(out var state, out var remainingScore)) {
4037
foreach (var (next, score) in Steps(map, state, forward: true)) {
41-
if (bestSpots.Contains(next)) {
42-
continue;
43-
}
44-
var nextScore = remainingScore - score;
45-
if (dist[next] == nextScore) {
38+
var nextRemainingScore = remainingScore - score;
39+
if (!bestSpots.Contains(next) && dist[next] == nextRemainingScore) {
4640
bestSpots.Add(next);
47-
q.Enqueue(next, nextScore);
41+
q.Enqueue(next, nextRemainingScore);
4842
}
4943
}
5044
}
5145
return bestSpots.DistinctBy(state => state.pos).Count();
5246
}
5347

54-
Dictionary<State, int> DistancesTo(Map map, Complex goal) {
55-
var res = new Dictionary<State, int>();
48+
Dictionary<State, int> Dijkstra(Map map, Complex goal) {
49+
// Dijkstra algorithm; works backwards from the goal returns the
50+
// distances to all tiles and directions.
51+
var dist = new Dictionary<State, int>();
5652

57-
// a flood fill algorithm, works backwards from the goal, and
58-
// computes the distances between any location in the map and the goal
5953
var q = new PriorityQueue<State, int>();
6054
foreach (var dir in Dirs) {
6155
q.Enqueue((goal, dir), 0);
62-
res[(goal, dir)] = 0;
56+
dist[(goal, dir)] = 0;
6357
}
6458

65-
while (q.TryDequeue(out var state, out var totalScore)) {
66-
if (totalScore != res[state]) {
67-
continue;
68-
}
69-
foreach (var (next, score) in Steps(map, state, forward: false)) {
70-
var nextCost = totalScore + score;
71-
if (res.ContainsKey(next) && res[next] < nextCost) {
72-
continue;
59+
while (q.TryDequeue(out var cur, out var totalDistance)) {
60+
foreach (var (next, score) in Steps(map, cur, forward: false)) {
61+
var nextCost = totalDistance + score;
62+
if (nextCost < dist.GetOrDefault(next, int.MaxValue)) {
63+
q.Remove(next, out _, out _, null);
64+
dist[next] = nextCost;
65+
q.Enqueue(next, nextCost);
7366
}
74-
75-
res[next] = nextCost;
76-
q.Enqueue(next, nextCost);
7767
}
7868
}
79-
return res;
69+
return dist;
8070
}
8171

82-
8372
// returns the possible next or previous states and the associated costs for a given state.
8473
// in forward mode we scan the possible states from the start state towards the goal.
8574
// in backward mode we are working backwards from the goal to the start.

0 commit comments

Comments
 (0)