mirror of
https://github.com/parnic/advent-of-code-2024.git
synced 2025-06-16 12:30:13 -05:00
Day 17
Part 2 was a doozy. I sought some help online for understanding what the program was doing and how to feed it an input to get the output we were after.
This commit is contained in:
318
src/17.cs
Normal file
318
src/17.cs
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace aoc2024;
|
||||||
|
|
||||||
|
internal class Day17 : Day
|
||||||
|
{
|
||||||
|
[DebuggerDisplay("A={A}, B={B}, C={C}")]
|
||||||
|
class Registers
|
||||||
|
{
|
||||||
|
public long A = 0;
|
||||||
|
public long B = 0;
|
||||||
|
public long C = 0;
|
||||||
|
|
||||||
|
public Registers()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Registers(long a, long b, long c)
|
||||||
|
{
|
||||||
|
A = a;
|
||||||
|
B = b;
|
||||||
|
C = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Registers(Registers other)
|
||||||
|
{
|
||||||
|
A = other.A;
|
||||||
|
B = other.B;
|
||||||
|
C = other.C;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ComputeResult
|
||||||
|
{
|
||||||
|
public int? InstructionPointer;
|
||||||
|
public int? Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Registers InitRegisters = new();
|
||||||
|
private int[] Program = [];
|
||||||
|
private int[] outputScratch = [];
|
||||||
|
|
||||||
|
internal override void Parse()
|
||||||
|
{
|
||||||
|
var lines = Util.Parsing.ReadAllLines($"{GetDay()}").ToList();
|
||||||
|
InitRegisters.A = int.Parse(lines[0].Split(' ')[2]);
|
||||||
|
InitRegisters.B = int.Parse(lines[1].Split(' ')[2]);
|
||||||
|
InitRegisters.C = int.Parse(lines[2].Split(' ')[2]);
|
||||||
|
|
||||||
|
Program = lines[4].Split(' ')[1].Split(',').Select(int.Parse).ToArray();
|
||||||
|
outputScratch = new int[Program.Length];
|
||||||
|
|
||||||
|
RunTests();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunTests()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
var r = new Registers{C = 9};
|
||||||
|
int[] program = [2, 6];
|
||||||
|
RunProgram(program, r);
|
||||||
|
Debug.Assert(r.B == 1);
|
||||||
|
|
||||||
|
r = new Registers{C = 9};
|
||||||
|
RunProgramOptimized(program, ref r.A, ref r.B, ref r.C);
|
||||||
|
Debug.Assert(r.B == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var r = new Registers{A = 10};
|
||||||
|
int[] program = [5, 0, 5, 1, 5, 4];
|
||||||
|
var output = RunProgram(program, r);
|
||||||
|
Debug.Assert(output.SequenceEqual([0, 1, 2]));
|
||||||
|
|
||||||
|
r = new Registers{A = 10};
|
||||||
|
var o2 = RunProgramOptimized(program, ref r.A, ref r.B, ref r.C);
|
||||||
|
Debug.Assert(o2.SequenceEqual([0,1,2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var r = new Registers{A = 2024};
|
||||||
|
int[] program = [0,1,5,4,3,0];
|
||||||
|
var output = RunProgram(program, r);
|
||||||
|
Debug.Assert(output.SequenceEqual([4,2,5,6,7,7,7,7,3,1,0]));
|
||||||
|
Debug.Assert(r.A == 0);
|
||||||
|
|
||||||
|
r = new Registers{A = 2024};
|
||||||
|
var o2 = RunProgramOptimized(program, ref r.A, ref r.B, ref r.C);
|
||||||
|
Debug.Assert(o2.SequenceEqual([4,2,5,6,7,7,7,7,3,1,0]));
|
||||||
|
Debug.Assert(r.A == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var r = new Registers{B = 29};
|
||||||
|
int[] program = [1,7];
|
||||||
|
RunProgram(program, r);
|
||||||
|
Debug.Assert(r.B == 26);
|
||||||
|
|
||||||
|
r = new Registers{B = 29};
|
||||||
|
RunProgramOptimized(program, ref r.A, ref r.B, ref r.C);
|
||||||
|
Debug.Assert(r.B == 26);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var r = new Registers{B = 2024, C = 43690};
|
||||||
|
int[] program = [4,0];
|
||||||
|
RunProgram(program, r);
|
||||||
|
Debug.Assert(r.B == 44354);
|
||||||
|
|
||||||
|
r = new Registers{B = 2024, C = 43690};
|
||||||
|
RunProgramOptimized(program, ref r.A, ref r.B, ref r.C);
|
||||||
|
Debug.Assert(r.B == 44354);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var r = new Registers{A = 117440};
|
||||||
|
int[] program = [0,3,5,4,3,0];
|
||||||
|
var output = RunProgram(program, r);
|
||||||
|
Debug.Assert(output.SequenceEqual([0,3,5,4,3,0]));
|
||||||
|
|
||||||
|
r = new Registers{A = 117440};
|
||||||
|
var o2 = RunProgramOptimized(program, ref r.A, ref r.B, ref r.C);
|
||||||
|
Debug.Assert(o2.SequenceEqual([0,3,5,4,3,0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long GetCombo(int operand, Registers registers)
|
||||||
|
{
|
||||||
|
return operand switch
|
||||||
|
{
|
||||||
|
0 or 1 or 2 or 3 => operand,
|
||||||
|
4 => registers.A,
|
||||||
|
5 => registers.B,
|
||||||
|
6 => registers.C,
|
||||||
|
_ => throw new Exception("invalid operand value for combo")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ComputeResult? Compute(int instruction, int operand, Registers registers)
|
||||||
|
{
|
||||||
|
switch (instruction)
|
||||||
|
{
|
||||||
|
// adv
|
||||||
|
case 0:
|
||||||
|
{
|
||||||
|
div(operand, registers, out registers.A);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bdv
|
||||||
|
case 6:
|
||||||
|
{
|
||||||
|
div(operand, registers, out registers.B);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cdv
|
||||||
|
case 7:
|
||||||
|
{
|
||||||
|
div(operand, registers, out registers.C);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bxl
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
var v1 = registers.B;
|
||||||
|
var v2 = operand;
|
||||||
|
var result = v1 ^ v2;
|
||||||
|
registers.B = result;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bst
|
||||||
|
case 2:
|
||||||
|
{
|
||||||
|
var v1 = GetCombo(operand, registers);
|
||||||
|
var result = v1 % 8;
|
||||||
|
registers.B = result;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// jnz
|
||||||
|
case 3:
|
||||||
|
{
|
||||||
|
if (registers.A == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ComputeResult {InstructionPointer = operand};
|
||||||
|
}
|
||||||
|
|
||||||
|
// bxc
|
||||||
|
case 4:
|
||||||
|
{
|
||||||
|
var v1 = registers.B;
|
||||||
|
var v2 = registers.C;
|
||||||
|
var result = v1 ^ v2;
|
||||||
|
registers.B = result;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// out
|
||||||
|
case 5:
|
||||||
|
{
|
||||||
|
var v1 = GetCombo(operand, registers);
|
||||||
|
var result = v1 % 8;
|
||||||
|
var truncated = (int) result;
|
||||||
|
return new ComputeResult {Output = truncated};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
static void div(int op, Registers regs, out long store)
|
||||||
|
{
|
||||||
|
var numer = regs.A;
|
||||||
|
var denom = BigInteger.Pow(2, (int)GetCombo(op, regs));
|
||||||
|
|
||||||
|
var result = numer / denom;
|
||||||
|
int truncated = (int)(result % int.MaxValue);
|
||||||
|
store = truncated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<int> RunProgram(int[] program, Registers registers)
|
||||||
|
{
|
||||||
|
List<int> output = [];
|
||||||
|
|
||||||
|
int instructionPointer = 0;
|
||||||
|
while (instructionPointer >= 0 && instructionPointer < program.Length - 1)
|
||||||
|
{
|
||||||
|
int instruction = program[instructionPointer];
|
||||||
|
int operand = program[instructionPointer + 1];
|
||||||
|
var result = Compute(instruction, operand, registers);
|
||||||
|
if (result?.InstructionPointer.HasValue == true)
|
||||||
|
{
|
||||||
|
instructionPointer = result.InstructionPointer.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
instructionPointer += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result?.Output != null)
|
||||||
|
{
|
||||||
|
output.Add(result.Output.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override string Part1()
|
||||||
|
{
|
||||||
|
var registers = new Registers(InitRegisters);
|
||||||
|
var output = RunProgram(Program, registers);
|
||||||
|
|
||||||
|
return $"Output: <+white>{string.Join(',', output)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReadOnlySpan<int> RunProgramOptimized(ReadOnlySpan<int> program, ref long a, ref long b, ref long c)
|
||||||
|
{
|
||||||
|
int instructionPointer = 0;
|
||||||
|
int lengthCurr = 0;
|
||||||
|
var val = new[] { 0, 1, 2, 3, a, b, c, 0 };
|
||||||
|
while (instructionPointer < program.Length)
|
||||||
|
{
|
||||||
|
(a, b, c) = (val[4], val[5], val[6]);
|
||||||
|
var instruction = program[instructionPointer];
|
||||||
|
var operand = program[instructionPointer + 1];
|
||||||
|
(instructionPointer, var result) = (instruction != 3 || a == 0 ? instructionPointer + 2 : operand, (int)val[operand]);
|
||||||
|
|
||||||
|
(val[4], val[5], val[6]) = instruction switch
|
||||||
|
{
|
||||||
|
0 => (a >> result, b, c),
|
||||||
|
1 => (a, b ^ operand, c),
|
||||||
|
2 => (a, val[operand] & 7, c),
|
||||||
|
4 => (a, b ^ c, c),
|
||||||
|
6 => (a, a >> result, c),
|
||||||
|
7 => (a, b, a >> result),
|
||||||
|
_ => (a, b, c)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (instruction == 5)
|
||||||
|
{
|
||||||
|
outputScratch[lengthCurr++] = result & 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(a, b, c) = (val[4], val[5], val[6]);
|
||||||
|
return outputScratch.AsSpan()[..lengthCurr];
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override string Part2()
|
||||||
|
{
|
||||||
|
long a = 0;
|
||||||
|
for (int i = 0; i < Program.Length; i++)
|
||||||
|
{
|
||||||
|
for (a <<= 3;; ++a)
|
||||||
|
{
|
||||||
|
long regA = a;
|
||||||
|
long regB = 0;
|
||||||
|
long regC = 0;
|
||||||
|
var outputSpan = RunProgramOptimized(Program, ref regA, ref regB, ref regC);
|
||||||
|
if (outputSpan[..(i + 1)].SequenceEqual(Program.AsSpan()[^(i + 1)..]))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"Value of A to repeat the program: <+white>{a}";
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user