This one was a real doozy. I was all kinds of twisted up trying to find a valid solution here and built + threw away at least 3 implementations that each _almost_ worked.
This commit is contained in:
2024-12-21 16:28:52 -06:00
parent 73a1b0567a
commit 296ac5a406
2 changed files with 299 additions and 0 deletions

272
src/21.cs Normal file
View File

@ -0,0 +1,272 @@
using aoc2024.Util;
namespace aoc2024;
internal class Day21 : Day
{
private List<string> codes = [];
private static readonly Dictionary<char, ivec2> numericButtonLayout =
new()
{
{'7', new ivec2(0, 0)},
{'8', new ivec2(1, 0)},
{'9', new ivec2(2, 0)},
{'4', new ivec2(0, 1)},
{'5', new ivec2(1, 1)},
{'6', new ivec2(2, 1)},
{'1', new ivec2(0, 2)},
{'2', new ivec2(1, 2)},
{'3', new ivec2(2, 2)},
{'0', new ivec2(1, 3)},
{'A', new ivec2(2, 3)},
};
private static readonly Dictionary<ivec2, char> reverseNumericButtonLayout =
new()
{
{new ivec2(0, 0), '7'},
{new ivec2(1, 0), '8'},
{new ivec2(2, 0), '9'},
{new ivec2(0, 1), '4'},
{new ivec2(1, 1), '5'},
{new ivec2(2, 1), '6'},
{new ivec2(0, 2), '1'},
{new ivec2(1, 2), '2'},
{new ivec2(2, 2), '3'},
{new ivec2(1, 3), '0'},
{new ivec2(2, 3), 'A'},
};
private static readonly Dictionary<char, ivec2> directionalKeypadLayout =
new()
{
{'^', new ivec2(1, 0)},
{'A', new ivec2(2, 0)},
{'<', new ivec2(0, 1)},
{'v', new ivec2(1, 1)},
{'>', new ivec2(2, 1)},
};
private static readonly Dictionary<ivec2, char> reverseDirectionalKeypadLayout =
new()
{
{new ivec2(1, 0), '^'},
{new ivec2(2, 0), 'A'},
{new ivec2(0, 1), '<'},
{new ivec2(1, 1), 'v'},
{new ivec2(2, 1), '>'},
};
private static readonly Dictionary<(char from, char to), List<string>> numericKeypadMoves = GetMoves(numericButtonLayout);
private static readonly Dictionary<(char from, char to), List<string>> directionalKeypadMoves = GetMoves(directionalKeypadLayout);
private static readonly Dictionary<(char from, char to), List<string>> allMoves = numericKeypadMoves.Where(k => !directionalKeypadMoves.ContainsKey(k.Key)).Union(directionalKeypadMoves).ToDictionary();
internal override void Parse()
{
codes = [..Util.Parsing.ReadAllLines($"{GetDay()}")];
}
private static ivec2? GetPosFromKey(char key, Dictionary<char, ivec2> layout)
{
if (!layout.TryGetValue(key, out var pos))
{
return null;
}
return pos;
}
private static char? GetKeyFromPos(ivec2 pos, Dictionary<char, ivec2> layout)
{
var dict = reverseDirectionalKeypadLayout;
if (layout.Count == numericButtonLayout.Count)
{
dict = reverseNumericButtonLayout;
}
if (!dict.TryGetValue(pos, out var key))
{
return null;
}
return key;
}
private static Dictionary<(char from, char to), List<string>> GetMoves(Dictionary<char, ivec2> keypad)
{
var keys = string.Concat(keypad.Select(k => k.Key));
Dictionary<(char from, char to), List<string>> keypadMoves = [];
for (int fromIdx = 0; fromIdx < keys.Length; fromIdx++)
{
char from = keys[fromIdx];
var keyPos = GetPosFromKey(from, keypad);
for (int toIdx = fromIdx; toIdx < keys.Length; toIdx++)
{
char to = keys[toIdx];
var pq = new PriorityQueue<ivec2, int>();
pq.Enqueue(keyPos!.Value, 0);
Dictionary<ivec2, (int cost, HashSet<string> options)> state = [];
state.Add(keyPos.Value, (0, [""]));
while (pq.TryDequeue(out var pos, out var _))
{
var (cost, options) = state[pos];
if (GetKeyFromPos(pos, keypad) == to)
{
keypadMoves.Add((from, to), [.. options]);
if (from != to)
{
HashSet<string> reverseOptions = [];
foreach (string s1 in options)
{
string rev = s1.Reverse()
.Aggregate("", (current, c) => current + c switch
{
'>' => '<',
'<' => ">",
'^' => 'v',
'v' => '^',
_ => c,
});
reverseOptions.Add(rev);
}
keypadMoves.Add((to, from), [.. reverseOptions]);
}
break;
}
foreach (var neighbor in pos.GetBoundedOrthogonalNeighbors(0, 0, keypad.Max(k => k.Value.x), keypad.Max(k => k.Value.y)))
{
if (GetKeyFromPos(pos, keypad) == null)
{
continue;
}
var newCost = cost + 1;
var hasSeen = state.TryGetValue(neighbor, out var thisState);
if (!hasSeen)
{
thisState = (newCost, []);
state[neighbor] = thisState;
}
if (newCost != thisState.cost)
{
continue;
}
foreach (string s in options)
{
thisState.options.Add(s + ivec2.Sign(neighbor - pos).CharFromDir());
}
if (!hasSeen)
{
pq.Enqueue(neighbor, newCost);
}
}
}
}
}
return keypadMoves;
}
private static List<string> GetSequenceList(string code, Dictionary<(char from, char to), List<string>> keypadMoves)
{
List<string> sequence = [""];
char prevKey = 'A';
foreach (var key in code)
{
var newSeq = new List<string>();
var moves = keypadMoves[(prevKey, key)];
foreach (var prevStrokes in sequence)
{
foreach (var nextStroke in moves)
{
newSeq.Add(prevStrokes + nextStroke + 'A');
}
}
prevKey = key;
sequence = newSeq;
}
return sequence;
}
private static long ShortestMovesTo(string current, int level, int stopLevel, Dictionary<(string key, int level), long> cache)
{
if (cache.TryGetValue((current, level), out var cost))
{
return cost;
}
if (level == stopLevel)
{
var result = GetSequenceList(current, allMoves).Select(a => a.Length).Min();
cache.Add((current, level), result);
return result;
}
var firstMoveIdx = current.IndexOf('A');
var firstMove = current[..(firstMoveIdx + 1)];
var remainingMoves = current[(firstMoveIdx + 1)..];
long shortest = long.MaxValue;
var sequences = GetSequenceList(firstMove, allMoves);
foreach (var seq in sequences)
{
long count = ShortestMovesTo(seq, level + 1, stopLevel, cache);
if (shortest > count)
{
shortest = count;
}
}
if (remainingMoves.Length > 0)
{
shortest += ShortestMovesTo(remainingMoves, level, stopLevel, cache);
}
cache.Add((current, level), shortest);
return shortest;
}
internal override string Part1()
{
var cache = new Dictionary<(string key, int level), long>();
long total = 0;
foreach (var code in codes)
{
var num = int.Parse(code[..3]);
var moveLen = ShortestMovesTo(code, 0, 2, cache);
total += moveLen * num;
}
return $"3-level code complexity sum: <+white>{total}";
}
internal override string Part2()
{
var cache = new Dictionary<(string key, int level), long>();
long total = 0;
foreach (var code in codes)
{
var num = int.Parse(code[..3]);
var moveLen = ShortestMovesTo(code, 0, 25, cache);
total += moveLen * num;
}
return $"26-level code complexity sum: <+white>{total}";
}
}

View File

@ -38,6 +38,8 @@ public readonly struct ivec2 : IEquatable<ivec2>, IComparable<ivec2>, IComparabl
public long ManhattanDistance => Abs(this).Sum;
public long ManhattanDistanceTo(ivec2 other) => System.Math.Abs(x - other.x) + System.Math.Abs(y - other.y);
public ivec2 ManhattanVectorTo(ivec2 other) => new(other.x - x, other.y - y);
public static ivec2 DirFromChar(char ch) => ch switch
{
'^' => UP,
@ -47,6 +49,31 @@ public readonly struct ivec2 : IEquatable<ivec2>, IComparable<ivec2>, IComparabl
_ => throw new FormatException($"Invalid direction {ch}"),
};
public char CharFromDir()
{
if (this == UP)
{
return '^';
}
if (this == RIGHT)
{
return '>';
}
if (this == LEFT)
{
return '<';
}
if (this == DOWN)
{
return 'v';
}
throw new FormatException($"Invalid direction vector {this}");
}
public ivec2 GetBestDirectionTo(ivec2 p)
{
ivec2 diff = p - this;