Skip to content

Commit

Permalink
2024/20
Browse files Browse the repository at this point in the history
  • Loading branch information
encse committed Dec 20, 2024
1 parent 29de4d0 commit 4c0a8fe
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 28 deletions.
15 changes: 14 additions & 1 deletion 2024/Day20/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
## --- Day 20: Race Condition ---
The Historians are quite pixelated again. This time, a massive, black building looms over you - you're [right outside](/2017/day/24) the CPU!
The Historians are quite pixelated again. This time, a massive, black building looms over you - you're _right outside_ the CPU!

While The Historians get to work, a nearby program sees that you're idle and challenges you to a <em>race</em>. Apparently, you've arrived just in time for the frequently-held <em>race condition</em> festival!

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

Here is your corrected text with improved grammar and flow:

The problem included a small but crucial hint: _there is only a single path from the start to the end_. Moreover, there are no dead ends in the input; it's just a single, continuous trace.

I created a function that returns the points of the track in from finish to start. This way, the index of an item in the array corresponds to its distance to the finish line. Then, I go over the path. For each position, the number of possible cheats is calculated by checking what happens if we are trying to make a shortcut to any other positions that is closer to the finish line.

There are a number of cases to consider:
- the target position is too far away. This happens when its Manhattan distance is greater than the allowed _cheat_ limit
- the target is within range, but the saving is less than 100
- the target is within range, and the saving is at least 100

We need to determine the number of good cheats for each position add them up. I used Parallel LINQ here, as the regular sequential one took significantly more time.
50 changes: 23 additions & 27 deletions 2024/Day20/Solution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,26 @@ class Solution : Solver {

int Solve(string input, int cheat) {
var path = GetPath(input);

// this nested loop is 6x times faster then the same thing with LINQ ¯\_(ツ)_/¯
var res = 0;
for (var i = 0; i < path.Length; i++) {
for (var j = i + 1; j < path.Length; j++) {
var dist = Manhattan(path[i], path[j]);

// the index of an element in the path equals to its distance
// from the finish line

var saving = j - (i + dist);
if (dist <= cheat && saving >= 100) {
res++;
}
}
}
return res;
var indices = Enumerable.Range(0, path.Length).ToArray();

// sum up the worthy cheats for each index along the path
var cheatsFromI = (int i) => (
from j in indices[0..i]
let dist = Manhattan(path[i], path[j])
let saving = i - (j + dist)
where dist <= cheat && saving >= 100
select 1
).Sum();

// parallel is gold today, it gives us an 3-4x boost
return indices.AsParallel().Select(cheatsFromI).Sum();
}

int Manhattan(Complex a, Complex b) =>
(int)(Math.Abs(a.Imaginary - b.Imaginary) + Math.Abs(a.Real - b.Real));

// follow the path from start to finish, supposed that there is a single track in the input
// follow the path from finish to start, supposed that there is a single track in the input
// the index of a position in the returned array equals to its distance from the finish
Complex[] GetPath(string input) {
var lines = input.Split("\n");
var map = (
Expand All @@ -44,19 +41,18 @@ from x in Enumerable.Range(0, lines[0].Length)
select new KeyValuePair<Complex, char>(x + y * Complex.ImaginaryOne, lines[y][x])
).ToDictionary();

Complex[] dirs = [-1, 1, Complex.ImaginaryOne, -Complex.ImaginaryOne];

var start = map.Keys.Single(k => map[k] == 'S');
var goal = map.Keys.Single(k => map[k] == 'E');

var (prev, cur, dir) = ((Complex?)null, start, Complex.ImaginaryOne);
var (prev, cur) = ((Complex?)null, goal);
var res = new List<Complex> { cur };

var res = new List<Complex> { start };
while (cur != goal) {
if (map[cur + dir] == '#' || cur + dir == prev) {
dir *= Complex.ImaginaryOne;
} else {
(prev, cur) = (cur, cur + dir);
res.Add(cur);
}
while (cur != start) {
var dir = dirs.Single(dir => map[cur + dir] != '#' && cur + dir != prev);
(prev, cur) = (cur, cur + dir);
res.Add(cur);
}
return res.ToArray();
}
Expand Down

0 comments on commit 4c0a8fe

Please sign in to comment.