Skip to content

Commit

Permalink
2024/21
Browse files Browse the repository at this point in the history
  • Loading branch information
encse committed Dec 22, 2024
1 parent c3fdcc1 commit e2e3491
Showing 1 changed file with 53 additions and 45 deletions.
98 changes: 53 additions & 45 deletions 2024/Day21/Solution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,81 +4,89 @@ namespace AdventOfCode.Y2024.Day21;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using AngleSharp.Common;
using Cache = System.Collections.Concurrent.ConcurrentDictionary<(char, char, int), long>;
using Keypad = System.Collections.Generic.Dictionary<System.Numerics.Complex, char>;
record struct Vec2(long x, long y);

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

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

public object PartOne(string input) {
return input.Split("\n").Sum(line => Solve2(line, 2));
}
public object PartTwo(string input) {
return input.Split("\n").Sum(line => Solve2(line, 25));
}

static readonly Complex Right = 1;
static readonly Complex Up = Complex.ImaginaryOne;
static readonly Complex Down = -Complex.ImaginaryOne;

long Solve2(string line, int depth) {
public object PartOne(string input) => Solve(input, 2);
public object PartTwo(string input) => Solve(input, 25);
long Solve(string input, int depth) {
var keypad1 = ParseKeypad("789\n456\n123\n 0A");
var keypad2 = ParseKeypad(" ^A\n<v>");
var keypads = Enumerable.Repeat(keypad2, depth).Prepend(keypad1).ToArray();
var res = EncodeString(line, keypads, new Cache());
return res * int.Parse(line.Substring(0, line.Length - 1));

var cache = new Cache();
var res = 0L;

foreach (var line in input.Split("\n")) {
var num = int.Parse(line[..^1]);
res += num * EncodeKeys(line, keypads, cache);
}
return res;
}

long EncodeString(string st, Keypad[] keypads, Cache cache) {
// Determines the length of the shortest sequence that is needed to enter the given
// keys. An empty keypad array means that the sequence is simply entered by a human
// and no further encoding is needed. Otherwise the sequence is entered by a robot
// which needs to be programmed. In practice this means that the keys are encoded
// using the robots keypad (the first keypad), generating an other sequence of keys.
// This other sequence is then recursively encoded using the rest of the keypads.
long EncodeKeys(string keys, Keypad[] keypads, Cache cache) {
if (keypads.Length == 0) {
return st.Length;
return keys.Length;
} else {

// the robot starts and finishes by pointing to 'A' key
// invariant: the robot starts and finishes by pointing at the 'A' key
var currentKey = 'A';
var length = 0L;
foreach (var nextKey in st) {

foreach (var nextKey in keys) {
length += EncodeKey(currentKey, nextKey, keypads, cache);
// while the sequence is entered the current key changes accordingly
currentKey = nextKey;
}
Debug.Assert(st.Last() == 'A', "The robot should point to the 'A' key");

// at the end the current key should be reset to 'A'
Debug.Assert(currentKey == 'A', "The robot should point at the 'A' key");
return length;
}
}
long EncodeKey(char currentKey, char nextKey, Keypad[] keypads, Cache cache) {
return cache.GetOrAdd((currentKey, nextKey, keypads.Length), _ => {
var currentPos = keypads[0].Single(kvp => kvp.Value == currentKey).Key;
var nextPos = keypads[0].Single(kvp => kvp.Value == nextKey).Key;
long EncodeKey(char currentKey, char nextKey, Keypad[] keypads, Cache cache) =>
cache.GetOrAdd((currentKey, nextKey, keypads.Length), _ => {
var keypad = keypads[0];

var dy = (int)(nextPos.Imaginary - currentPos.Imaginary);
var dx = (int)(nextPos.Real - currentPos.Real);
var currentPos = keypad.Single(kvp => kvp.Value == currentKey).Key;
var nextPos = keypad.Single(kvp => kvp.Value == nextKey).Key;

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

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

if (keypads[0][currentPos + dy * Up] != ' ') {
cost = Math.Min(cost, EncodeString($"{vert}{horiz}A", keypads[1..], cache));
}

if (keypads[0][currentPos + dx * Right] != ' ') {
cost = Math.Min(cost, EncodeString($"{horiz}{vert}A", keypads[1..], cache));
}
return cost;
});
}
var cost = long.MaxValue;
// we can usually go vertical first then horizontal or vica versa,
// but we should check for the extra condition and don't position
// the robot over the ' ' key:
if (keypad[new Vec2(currentPos.x, nextPos.y)] != ' ') {
cost = Math.Min(cost, EncodeKeys($"{vert}{horiz}A", keypads[1..], cache));
}

if (keypad[new Vec2(nextPos.x, currentPos.y)] != ' ') {
cost = Math.Min(cost, EncodeKeys($"{horiz}{vert}A", keypads[1..], cache));
}
return cost;
});

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

0 comments on commit e2e3491

Please sign in to comment.