Skip to content

Commit

Permalink
2024/16 make it more disktra like
Browse files Browse the repository at this point in the history
  • Loading branch information
encse committed Dec 16, 2024
1 parent c4aa58a commit fb89957
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 38 deletions.
6 changes: 3 additions & 3 deletions 2024/Day16/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ You and The Historians arrive to search for the Chief right as the event is abou

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

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`.
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`.

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.
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_.

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.
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.
59 changes: 24 additions & 35 deletions 2024/Day16/Solution.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
namespace AdventOfCode.Y2024.Day16;

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Numerics;
using AngleSharp.Common;
using Map = System.Collections.Generic.Dictionary<System.Numerics.Complex, char>;
using State = (System.Numerics.Complex pos, System.Numerics.Complex dir);

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

public object PartOne(string input) => FindDistance(GetMap(input));
public object PartOne(string input) => FindBestScore(GetMap(input));
public object PartTwo(string input) => FindBestSpots(GetMap(input));

int FindDistance(Map map) {
var dist = DistancesTo(map, Goal(map));
return dist[Start(map)];
}
int FindBestScore(Map map) => Dijkstra(map, Goal(map))[Start(map)];

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

// flood fill algorithm determines the best spots by following the shortest paths
// using the distance map as guideline.

// track the shortest paths using the distance map as guideline.
var q = new PriorityQueue<State, int>();
q.Enqueue(start, dist[start]);
var bestSpots = new HashSet<State> { start };

var bestSpots = new HashSet<State> { start };
while (q.TryDequeue(out var state, out var remainingScore)) {
foreach (var (next, score) in Steps(map, state, forward: true)) {
if (bestSpots.Contains(next)) {
continue;
}
var nextScore = remainingScore - score;
if (dist[next] == nextScore) {
var nextRemainingScore = remainingScore - score;
if (!bestSpots.Contains(next) && dist[next] == nextRemainingScore) {
bestSpots.Add(next);
q.Enqueue(next, nextScore);
q.Enqueue(next, nextRemainingScore);
}
}
}
return bestSpots.DistinctBy(state => state.pos).Count();
}

Dictionary<State, int> DistancesTo(Map map, Complex goal) {
var res = new Dictionary<State, int>();
Dictionary<State, int> Dijkstra(Map map, Complex goal) {
// Dijkstra algorithm; works backwards from the goal returns the
// distances to all tiles and directions.
var dist = new Dictionary<State, int>();

// a flood fill algorithm, works backwards from the goal, and
// computes the distances between any location in the map and the goal
var q = new PriorityQueue<State, int>();
foreach (var dir in Dirs) {
q.Enqueue((goal, dir), 0);
res[(goal, dir)] = 0;
dist[(goal, dir)] = 0;
}

while (q.TryDequeue(out var state, out var totalScore)) {
if (totalScore != res[state]) {
continue;
}
foreach (var (next, score) in Steps(map, state, forward: false)) {
var nextCost = totalScore + score;
if (res.ContainsKey(next) && res[next] < nextCost) {
continue;
while (q.TryDequeue(out var cur, out var totalDistance)) {
foreach (var (next, score) in Steps(map, cur, forward: false)) {
var nextCost = totalDistance + score;
if (nextCost < dist.GetOrDefault(next, int.MaxValue)) {
q.Remove(next, out _, out _, null);
dist[next] = nextCost;
q.Enqueue(next, nextCost);
}

res[next] = nextCost;
q.Enqueue(next, nextCost);
}
}
return res;
return dist;
}


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

0 comments on commit fb89957

Please sign in to comment.