Skip to content

Commit e2e3491

Browse files
committed
2024/21
1 parent c3fdcc1 commit e2e3491

File tree

1 file changed

+53
-45
lines changed

1 file changed

+53
-45
lines changed

2024/Day21/Solution.cs

+53-45
Original file line numberDiff line numberDiff line change
@@ -4,81 +4,89 @@ namespace AdventOfCode.Y2024.Day21;
44
using System.Collections.Generic;
55
using System.Diagnostics;
66
using System.Linq;
7-
using System.Numerics;
8-
using AngleSharp.Common;
9-
using Cache = System.Collections.Concurrent.ConcurrentDictionary<(char, char, int), long>;
10-
using Keypad = System.Collections.Generic.Dictionary<System.Numerics.Complex, char>;
11-
record struct Vec2(long x, long y);
127

8+
using Cache = System.Collections.Concurrent.ConcurrentDictionary<(char currentKey, char nextKey, int depth), long>;
9+
using Keypad = System.Collections.Generic.Dictionary<Vec2, char>;
10+
record struct Vec2(int x, int y);
1311

1412
[ProblemName("Keypad Conundrum")]
1513
class Solution : Solver {
1614

17-
public object PartOne(string input) {
18-
return input.Split("\n").Sum(line => Solve2(line, 2));
19-
}
20-
public object PartTwo(string input) {
21-
return input.Split("\n").Sum(line => Solve2(line, 25));
22-
}
23-
24-
static readonly Complex Right = 1;
25-
static readonly Complex Up = Complex.ImaginaryOne;
26-
static readonly Complex Down = -Complex.ImaginaryOne;
27-
28-
long Solve2(string line, int depth) {
15+
public object PartOne(string input) => Solve(input, 2);
16+
public object PartTwo(string input) => Solve(input, 25);
17+
long Solve(string input, int depth) {
2918
var keypad1 = ParseKeypad("789\n456\n123\n 0A");
3019
var keypad2 = ParseKeypad(" ^A\n<v>");
3120
var keypads = Enumerable.Repeat(keypad2, depth).Prepend(keypad1).ToArray();
32-
var res = EncodeString(line, keypads, new Cache());
33-
return res * int.Parse(line.Substring(0, line.Length - 1));
21+
22+
var cache = new Cache();
23+
var res = 0L;
24+
25+
foreach (var line in input.Split("\n")) {
26+
var num = int.Parse(line[..^1]);
27+
res += num * EncodeKeys(line, keypads, cache);
28+
}
29+
return res;
3430
}
3531

36-
long EncodeString(string st, Keypad[] keypads, Cache cache) {
32+
// Determines the length of the shortest sequence that is needed to enter the given
33+
// keys. An empty keypad array means that the sequence is simply entered by a human
34+
// and no further encoding is needed. Otherwise the sequence is entered by a robot
35+
// which needs to be programmed. In practice this means that the keys are encoded
36+
// using the robots keypad (the first keypad), generating an other sequence of keys.
37+
// This other sequence is then recursively encoded using the rest of the keypads.
38+
long EncodeKeys(string keys, Keypad[] keypads, Cache cache) {
3739
if (keypads.Length == 0) {
38-
return st.Length;
40+
return keys.Length;
3941
} else {
40-
41-
// the robot starts and finishes by pointing to 'A' key
42+
// invariant: the robot starts and finishes by pointing at the 'A' key
4243
var currentKey = 'A';
4344
var length = 0L;
44-
foreach (var nextKey in st) {
45+
46+
foreach (var nextKey in keys) {
4547
length += EncodeKey(currentKey, nextKey, keypads, cache);
48+
// while the sequence is entered the current key changes accordingly
4649
currentKey = nextKey;
4750
}
48-
Debug.Assert(st.Last() == 'A', "The robot should point to the 'A' key");
51+
52+
// at the end the current key should be reset to 'A'
53+
Debug.Assert(currentKey == 'A', "The robot should point at the 'A' key");
4954
return length;
5055
}
5156
}
52-
long EncodeKey(char currentKey, char nextKey, Keypad[] keypads, Cache cache) {
53-
return cache.GetOrAdd((currentKey, nextKey, keypads.Length), _ => {
54-
var currentPos = keypads[0].Single(kvp => kvp.Value == currentKey).Key;
55-
var nextPos = keypads[0].Single(kvp => kvp.Value == nextKey).Key;
57+
long EncodeKey(char currentKey, char nextKey, Keypad[] keypads, Cache cache) =>
58+
cache.GetOrAdd((currentKey, nextKey, keypads.Length), _ => {
59+
var keypad = keypads[0];
5660

57-
var dy = (int)(nextPos.Imaginary - currentPos.Imaginary);
58-
var dx = (int)(nextPos.Real - currentPos.Real);
61+
var currentPos = keypad.Single(kvp => kvp.Value == currentKey).Key;
62+
var nextPos = keypad.Single(kvp => kvp.Value == nextKey).Key;
5963

60-
var vert = new string(dy < 0 ? 'v' : '^', Math.Abs(dy));
61-
var horiz = new string(dx < 0 ? '<' : '>', Math.Abs(dx));
64+
var dy = nextPos.y - currentPos.y;
65+
var vert = new string(dy < 0 ? 'v' : '^', Math.Abs(dy));
6266

63-
var cost = long.MaxValue;
67+
var dx = nextPos.x - currentPos.x;
68+
var horiz = new string(dx < 0 ? '<' : '>', Math.Abs(dx));
6469

65-
if (keypads[0][currentPos + dy * Up] != ' ') {
66-
cost = Math.Min(cost, EncodeString($"{vert}{horiz}A", keypads[1..], cache));
67-
}
68-
69-
if (keypads[0][currentPos + dx * Right] != ' ') {
70-
cost = Math.Min(cost, EncodeString($"{horiz}{vert}A", keypads[1..], cache));
71-
}
72-
return cost;
73-
});
74-
}
70+
var cost = long.MaxValue;
71+
// we can usually go vertical first then horizontal or vica versa,
72+
// but we should check for the extra condition and don't position
73+
// the robot over the ' ' key:
74+
if (keypad[new Vec2(currentPos.x, nextPos.y)] != ' ') {
75+
cost = Math.Min(cost, EncodeKeys($"{vert}{horiz}A", keypads[1..], cache));
76+
}
77+
78+
if (keypad[new Vec2(nextPos.x, currentPos.y)] != ' ') {
79+
cost = Math.Min(cost, EncodeKeys($"{horiz}{vert}A", keypads[1..], cache));
80+
}
81+
return cost;
82+
});
7583

7684
Keypad ParseKeypad(string keypad) {
7785
var lines = keypad.Split("\n");
7886
return (
7987
from y in Enumerable.Range(0, lines.Length)
8088
from x in Enumerable.Range(0, lines[0].Length)
81-
select new KeyValuePair<Complex, char>(x + y * Down, lines[y][x])
89+
select new KeyValuePair<Vec2, char>(new Vec2(x, -y), lines[y][x])
8290
).ToDictionary();
8391
}
8492
}

0 commit comments

Comments
 (0)