From d3520308c2b05fb52d493e42d6080251f6046bc7 Mon Sep 17 00:00:00 2001 From: Tyler Beckman Date: Wed, 18 Dec 2024 20:20:00 -0700 Subject: [PATCH] Day 17 --- Cargo.lock | 83 ++++++++++++++++++++++++++- Cargo.toml | 15 ++++- cli/mod.ts | 2 +- day17/part1.rs | 150 +++++++++++++++++++++++++++++++++++++++++++++++++ day17/part2.rs | 58 +++++++++++++++++++ 5 files changed, 305 insertions(+), 3 deletions(-) create mode 100644 day17/part1.rs create mode 100644 day17/part2.rs diff --git a/Cargo.lock b/Cargo.lock index 5d41a28..b7fb020 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,7 +1,88 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aoc2024" version = "0.1.0" +dependencies = [ + "itertools", + "num-derive", + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" diff --git a/Cargo.toml b/Cargo.toml index 81d25e9..12004eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,11 @@ name = "aoc2024" version = "0.1.0" edition = "2021" +[dependencies] +itertools = "0.13.0" +num-derive = "0.4.2" +num-traits = "0.2.19" + [[bin]] name = "d11p2" path = "day11/part2.rs" @@ -29,4 +34,12 @@ path = "day15/part1.rs" [[bin]] name = "d15p2" -path = "day15/part2.rs" \ No newline at end of file +path = "day15/part2.rs" + +[[bin]] +name = "d17p1" +path = "day17/part1.rs" + +[[bin]] +name = "d17p2" +path = "day17/part2.rs" diff --git a/cli/mod.ts b/cli/mod.ts index 5b84bae..91fa8c3 100755 --- a/cli/mod.ts +++ b/cli/mod.ts @@ -230,7 +230,7 @@ if (import.meta.main) { }); if (inputResponse.status === 200) { - console.log(await inputResponse.text()) + Deno.stdout.write(await inputResponse.bytes()) } else { console.log(`Error fetching input for day ${day}:\n\n${await inputResponse.text()}`) } diff --git a/day17/part1.rs b/day17/part1.rs new file mode 100644 index 0000000..ebb603b --- /dev/null +++ b/day17/part1.rs @@ -0,0 +1,150 @@ +use std::collections::LinkedList; + +use itertools::Itertools; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; + +struct Register { + a: u64, + b: u64, + c: u64, +} + +enum Instruction { + OpCode(OpCode), + Operand(Operand) +} + +#[repr(u8)] +#[derive(FromPrimitive, Clone, Copy)] +enum OpCode { + /// Performs division + /// A register <- (A register)/(2^(combo operand)) + /// !!round towards 0!! + Adv = 0, + /// Calculates bitwise XOR + /// B register <- (B register) ^ (literal operand) + Bxl = 1, + /// Calculates modulo 8 (thereby keeping only its lowest 3 bits) + /// (combo operand) % 8 + Bst = 2, + /// Conditional jump + /// If (A register) == 0: do nothing + /// Else: Jump to literal operand + /// !!if this instruction jumps, the instruction pointer is not increased by 2 after this instruction.!! + Jnz = 3, + /// Calculates bitwise XOR + /// B register <- (B register) ^ (C register) + /// !!reads the operand, but ignores it!! + Bxc = 4, + /// Outputs data mod 8 + /// print ((combo operand) % 8) + /// !!multiple outputs are separated by commas!! + Out = 5, + /// Performs division + /// B register <- (A register) / (2^(combo operand)) + /// !!round towards 0!! + Bdv = 6, + /// Performs division + /// C register <- (A register) / (2^(combo operand)) + /// !!round towards 0!! + Cdv = 7, +} + +#[repr(u8)] +enum Operand { + Literal(u8), + RegisterA = 4, + RegisterB = 5, + RegisterC = 6, + Reserved = 7 +} + +impl Operand { + fn from_opcode(opcode: OpCode, num: u8) -> Self { + match opcode { + OpCode::Bxl | OpCode::Jnz | OpCode::Bxc => Operand::Literal(num), + OpCode::Adv | OpCode::Bst | OpCode::Out | OpCode::Bdv | OpCode::Cdv => match num { + 0..=3 => Operand::Literal(num), + 4 => Operand::RegisterA, + 5 => Operand::RegisterB, + 6 => Operand::RegisterC, + 7 | _ => unreachable!(), + }, + } + } + + fn get_value(&self, register: &mut Register) -> u64 { + match self { + Operand::Literal(n) => (*n).into(), + Operand::RegisterA => register.a, + Operand::RegisterB => register.b, + Operand::RegisterC => register.c, + Operand::Reserved => unreachable!(), + } + } +} + +const INPUT: &str = include_str!("input.txt"); + +fn main() { + // Input parsing + let (registers, instructions) = INPUT.split_once("\n\n").unwrap(); + let mut register = registers + .split("\n") + .map(|l| l[12..].parse::().unwrap()) + .enumerate() + .fold(Register { a: 0, b: 0, c: 0 }, |mut acc, (i, v)| { + match i { + 0 => acc.a = v, + 1 => acc.b = v, + 2 => acc.c = v, + _ => unreachable!() + }; + acc + }); + let instructions = instructions[9..] + .trim_ascii_end() + .split(",") + .chunks(2) + .into_iter() + .flat_map(|mut chunk| { + let opcode = OpCode::from_u8(chunk.next().unwrap().parse::().unwrap()).unwrap(); + [ + Instruction::OpCode(opcode), + Instruction::Operand(Operand::from_opcode(opcode, chunk.next().unwrap().parse::().unwrap())) + ] + }) + .collect_vec(); + let mut instruction_pointer = 0; + + // Actual logic + loop { + let Some(Instruction::OpCode(opcode)) = instructions.get(instruction_pointer) else { + break; + }; + let Some(Instruction::Operand(operand)) = instructions.get(instruction_pointer + 1) else { + break; + }; + let operand = operand.get_value(&mut register); + + match opcode { + OpCode::Adv => register.a = register.a / 2u64.pow(operand.try_into().unwrap()), + OpCode::Bxl => register.b = register.b ^ operand, + OpCode::Bst => register.b = operand % 8, + OpCode::Jnz => if register.a != 0 { + instruction_pointer = operand.try_into().unwrap(); + continue; + }, + OpCode::Bxc => register.b = register.b ^ register.c, + OpCode::Out => print!("{},", register.b % 8), + OpCode::Bdv => register.b = register.a / 2u64.pow(operand.try_into().unwrap()), + OpCode::Cdv => register.c = register.a / 2u64.pow(operand.try_into().unwrap()), + } + + instruction_pointer += 2; + } + + // Erase last comma + print!("\x08 \n"); +} diff --git a/day17/part2.rs b/day17/part2.rs new file mode 100644 index 0000000..946ffc0 --- /dev/null +++ b/day17/part2.rs @@ -0,0 +1,58 @@ +#![feature(vec_push_within_capacity)] +use std::collections::VecDeque; + +use itertools::Itertools; + +const INPUT: &str = include_str!("input.txt"); + +// I manually transpiled my program input to rust code, and then removed everything after the first print +// I hope this doesn't count as including my input, please have mercy o lord Eric Wastl +fn calculate(value: u64) -> u64 { + let mut register = (value, 0, 0); + + register.1 = register.0 & 0b111; // Take the lowest 3 bits + register.1 ^= 0b010; // XOR Depends on lowest 3 bits + register.2 = register.0 / 2u64.pow(register.1.try_into().unwrap()); // Remove some bits of A depending on its last 3 + register.1 ^= 0b011; // XOR Depends on lowest 3 bits + register.1 ^= register.2; // XOR Depends on lowest 3 and full + return register.1 & 0b111; +} + +fn main() { + // Input parsing + let (_, instructions) = INPUT.split_once("\n\n").unwrap(); + let instructions = instructions[9..] + .trim_ascii_end() + .split(",") + .into_iter() + .map(|num| { + num.parse::().unwrap() + }) + .collect_vec(); + + // Since the program operates in chunks of 3 + // And since each chunk relies on the chunks above it + // If we start from the last digit then we can get that right, and work from there + + // We have to use a queue/stack, because multiple inputs can get a correct digit, so we have to support branching + let mut branches = VecDeque::<(u64, usize)>::with_capacity(instructions.len() * 3); + branches.push_back((0b000, 0)); + let mut results = Vec::::new(); + loop { + let Some((next, digits_calculated)) = branches.pop_back() else { + break; + }; + + for i in 0b000..=0b111u64 { + if calculate(next | i) == instructions[15 - digits_calculated] { + if digits_calculated == 15 { + results.push(next | i); + } else { + branches.push_back(((next | i) << 3, digits_calculated + 1)); + } + } + } + } + + println!("Result: {}", results.iter().min().unwrap()); +}