From 6876ce9b64a755bc1858eec4511e4f51821874fd Mon Sep 17 00:00:00 2001 From: red Date: Wed, 3 Apr 2024 04:38:36 +0200 Subject: [PATCH] Year 2017: Day 23+24+25 --- CHANGELOG.md | 6 ++ Cargo.toml | 2 +- NOTES_2017.md | 12 +++ README.md | 2 +- benches/year_2017.rs | 50 +++++++++- inputs/year_2017/day_23_input | 32 ++++++ inputs/year_2017/day_24_input | 56 +++++++++++ inputs/year_2017/day_25_input | 62 ++++++++++++ src/year_2017.rs | 26 +++++ src/year_2017/day_23.rs | 134 +++++++++++++++++++++++++ src/year_2017/day_24.rs | 117 ++++++++++++++++++++++ src/year_2017/day_25.rs | 179 ++++++++++++++++++++++++++++++++++ 12 files changed, 675 insertions(+), 3 deletions(-) create mode 100644 inputs/year_2017/day_23_input create mode 100644 inputs/year_2017/day_24_input create mode 100644 inputs/year_2017/day_25_input create mode 100644 src/year_2017/day_23.rs create mode 100644 src/year_2017/day_24.rs create mode 100644 src/year_2017/day_25.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4774130..774de31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ Of note: - The changelog 2015.5.2 has been rewritten from each commit content. - This file may be amended entirely in the future to adhere to the [GNU Changelog style](https://www.gnu.org/prep/standards/html_node/Style-of-Change-Logs.html#Style-of-Change-Logs) +## [2017.25.1] +### Added +- Solved [exercice for 2017, day 23](src/year_2017/23.rs). +- Solved [exercice for 2017, day 24](src/year_2017/24.rs). +- Solved [exercice for 2017, day 25](src/year_2017/25.rs). + ## [2017.22.1] ### Added - Solved [exercice for 2017, day 22](src/year_2017/22.rs). diff --git a/Cargo.toml b/Cargo.toml index ddc5040..c24a130 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "advent-rs" -version = "2017.22.1" +version = "2017.25.1" edition = "2021" authors = ["Arnaud 'red' Rouyer"] readme = "README.md" diff --git a/NOTES_2017.md b/NOTES_2017.md index 69c027b..b72b803 100644 --- a/NOTES_2017.md +++ b/NOTES_2017.md @@ -91,3 +91,15 @@ That one was both funny and very annoying. Playing with patterns and turning the ## Day 22: Sporifica Virus Very funny exercise, but I'll spend a long time trying to optimize it. + +## Day 23: Coprocessor Conflagration + +This exercise [is actually a trap](https://www.youtube.com/watch?v=4F4qzPbcFiA): running it as expected would last you around ten minutes. Were you to [rewrite it as pseudo-code](https://docs.rs/advent-of-code/2022.0.66/src/advent_of_code/year2017/day23.rs.html#15), you would see another algorithm, that is [way easier to implement](https://github.com/galenelias/AdventOfCode_2017/blob/master/src/Day23/mod.rs#L70). + +## Day 24: Electromagnetic Moat + +Your usual shortest-path algorithm. + +## Day 25: The Halting Problem + +Implementing a [Turing machine](https://en.wikipedia.org/wiki/Turing_machine) isn't that hard once you get to the principle. Hardest part was actually parsing the input. diff --git a/README.md b/README.md index c6be75a..f7a1a93 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ As I said, [Consistency is hard](https://github.com/joshleaves/advent-rb), and I I'm also adding notes that may be useful if you're (like me) discovering Rust: - [2015, complete!](NOTES_2015.md) - [2016, complete!](NOTES_2016.md) -- [2017, up to day 22](NOTES_2017.md) +- [2017, complete!](NOTES_2017.md) # Regarding style rules I'm gonna use a mix of what `cargo fmt` does, with some stuff that feels more natural to me. diff --git a/benches/year_2017.rs b/benches/year_2017.rs index 2af63d1..0efe36e 100644 --- a/benches/year_2017.rs +++ b/benches/year_2017.rs @@ -20,6 +20,9 @@ use advent_rs::year_2017::day_19; use advent_rs::year_2017::day_20; use advent_rs::year_2017::day_21; use advent_rs::year_2017::day_22; +use advent_rs::year_2017::day_23; +use advent_rs::year_2017::day_24; +use advent_rs::year_2017::day_25; use criterion::{black_box, criterion_group, criterion_main, Criterion}; fn year_2017_day_01(c: &mut Criterion) { @@ -352,6 +355,48 @@ fn year_2017_day_22(c: &mut Criterion) { g2017_day_22.finish(); } +fn year_2017_day_23(c: &mut Criterion) { + let input = include_str!("../inputs/year_2017/day_23_input"); + assert_eq!(day_23::day_23_v1(input), 6_724); + assert_eq!(day_23::day_23_v2(input), 903); + + let mut g2017_day_23 = c.benchmark_group("year_2017::day_23"); + g2017_day_23.bench_function("year_2017::day_23_v1", |b| { + b.iter(|| day_23::day_23_v1(black_box(input))) + }); + g2017_day_23.bench_function("year_2017::day_23_v2", |b| { + b.iter(|| day_23::day_23_v2(black_box(input))) + }); + g2017_day_23.finish(); +} + +fn year_2017_day_24(c: &mut Criterion) { + let input = include_str!("../inputs/year_2017/day_24_input"); + assert_eq!(day_24::day_24_v1(input), 1_906); + assert_eq!(day_24::day_24_v2(input), 1_824); + + let mut g2017_day_24 = c.benchmark_group("year_2017::day_24"); + g2017_day_24.bench_function("year_2017::day_24_v1", |b| { + b.iter(|| day_24::day_24_v1(black_box(input))) + }); + g2017_day_24.bench_function("year_2017::day_24_v2", |b| { + b.iter(|| day_24::day_24_v2(black_box(input))) + }); + g2017_day_24.finish(); +} + +fn year_2017_day_25(c: &mut Criterion) { + let input = include_str!("../inputs/year_2017/day_25_input"); + assert_eq!(day_25::day_25(input), 4_387); + + let mut g2017_day_25 = c.benchmark_group("year_2017::day_25"); + g2017_day_25.bench_function("year_2017::day_25", |b| { + b.iter(|| day_25::day_25(black_box(input))) + }); + + g2017_day_25.finish(); +} + criterion_group!( benches, year_2017_day_01, @@ -375,6 +420,9 @@ criterion_group!( year_2017_day_19, year_2017_day_20, year_2017_day_21, - year_2017_day_22 + year_2017_day_22, + year_2017_day_23, + year_2017_day_24, + year_2017_day_25 ); criterion_main!(benches); diff --git a/inputs/year_2017/day_23_input b/inputs/year_2017/day_23_input new file mode 100644 index 0000000..30229f6 --- /dev/null +++ b/inputs/year_2017/day_23_input @@ -0,0 +1,32 @@ +set b 84 +set c b +jnz a 2 +jnz 1 5 +mul b 100 +sub b -100000 +set c b +sub c -17000 +set f 1 +set d 2 +set e 2 +set g d +mul g e +sub g b +jnz g 2 +set f 0 +sub e -1 +set g e +sub g b +jnz g -8 +sub d -1 +set g d +sub g b +jnz g -13 +jnz f 2 +sub h -1 +set g b +sub g c +jnz g 2 +jnz 1 3 +sub b -17 +jnz 1 -23 \ No newline at end of file diff --git a/inputs/year_2017/day_24_input b/inputs/year_2017/day_24_input new file mode 100644 index 0000000..2020f29 --- /dev/null +++ b/inputs/year_2017/day_24_input @@ -0,0 +1,56 @@ +31/13 +34/4 +49/49 +23/37 +47/45 +32/4 +12/35 +37/30 +41/48 +0/47 +32/30 +12/5 +37/31 +7/41 +10/28 +35/4 +28/35 +20/29 +32/20 +31/43 +48/14 +10/11 +27/6 +9/24 +8/28 +45/48 +8/1 +16/19 +45/45 +0/4 +29/33 +2/5 +33/9 +11/7 +32/10 +44/1 +40/32 +2/45 +16/16 +1/18 +38/36 +34/24 +39/44 +32/37 +26/46 +25/33 +9/10 +0/29 +38/8 +33/33 +49/19 +18/20 +49/39 +18/39 +26/13 +19/32 \ No newline at end of file diff --git a/inputs/year_2017/day_25_input b/inputs/year_2017/day_25_input new file mode 100644 index 0000000..bc7bb2c --- /dev/null +++ b/inputs/year_2017/day_25_input @@ -0,0 +1,62 @@ +Begin in state A. +Perform a diagnostic checksum after 12208951 steps. + +In state A: + If the current value is 0: + - Write the value 1. + - Move one slot to the right. + - Continue with state B. + If the current value is 1: + - Write the value 0. + - Move one slot to the left. + - Continue with state E. + +In state B: + If the current value is 0: + - Write the value 1. + - Move one slot to the left. + - Continue with state C. + If the current value is 1: + - Write the value 0. + - Move one slot to the right. + - Continue with state A. + +In state C: + If the current value is 0: + - Write the value 1. + - Move one slot to the left. + - Continue with state D. + If the current value is 1: + - Write the value 0. + - Move one slot to the right. + - Continue with state C. + +In state D: + If the current value is 0: + - Write the value 1. + - Move one slot to the left. + - Continue with state E. + If the current value is 1: + - Write the value 0. + - Move one slot to the left. + - Continue with state F. + +In state E: + If the current value is 0: + - Write the value 1. + - Move one slot to the left. + - Continue with state A. + If the current value is 1: + - Write the value 1. + - Move one slot to the left. + - Continue with state C. + +In state F: + If the current value is 0: + - Write the value 1. + - Move one slot to the left. + - Continue with state E. + If the current value is 1: + - Write the value 1. + - Move one slot to the right. + - Continue with state A. \ No newline at end of file diff --git a/src/year_2017.rs b/src/year_2017.rs index 24c9394..26ed5a4 100644 --- a/src/year_2017.rs +++ b/src/year_2017.rs @@ -26,6 +26,9 @@ pub mod day_19; pub mod day_20; pub mod day_21; pub mod day_22; +pub mod day_23; +pub mod day_24; +pub mod day_25; pub fn solve(day: u8, part: u8, input: impl Into) -> Option { if part > 2 { @@ -55,6 +58,9 @@ pub fn solve(day: u8, part: u8, input: impl Into) -> Option { 20 => Some(day_20::day_20(part, input).to_string()), 21 => Some(day_21::day_21(part, input).to_string()), 22 => Some(day_22::day_22(part, input).to_string()), + 23 => Some(day_23::day_23(part, input).to_string()), + 24 => Some(day_24::day_24(part, input).to_string()), + 25 => Some(day_25::day_25(input).to_string()), _ => None, } } @@ -216,4 +222,24 @@ mod tests { assert_eq!(day_22::day_22_v1(input), 5_246); assert_eq!(day_22::day_22_v2(input), 2_512_059); } + + #[test] + fn day_23() { + let input = include_str!("../inputs/year_2017/day_23_input"); + assert_eq!(day_23::day_23_v1(input), 6_724); + assert_eq!(day_23::day_23_v2(input), 903); + } + + #[test] + fn day_24() { + let input = include_str!("../inputs/year_2017/day_24_input"); + assert_eq!(day_24::day_24_v1(input), 1_906); + assert_eq!(day_24::day_24_v2(input), 1_824); + } + + #[test] + fn day_25() { + let input = include_str!("../inputs/year_2017/day_25_input"); + assert_eq!(day_25::day_25(input), 4_387); + } } diff --git a/src/year_2017/day_23.rs b/src/year_2017/day_23.rs new file mode 100644 index 0000000..d901600 --- /dev/null +++ b/src/year_2017/day_23.rs @@ -0,0 +1,134 @@ +use itertools::Itertools; + +#[derive(Clone, Debug)] +enum VoR { + Value(i32), + Register(usize), +} + +impl VoR { + fn new(input: &str) -> Self { + let bytes = input.as_bytes(); + match bytes[0] { + b'a'..=b'h' => VoR::Register((bytes[0] - b'a') as usize), + b'0'..=b'9' | b'-' => VoR::Value(input.parse::().unwrap()), + _ => panic!("Invalid value: _{}_", input), + } + } +} + +#[derive(Clone, Debug)] +enum Instruction { + Set(usize, VoR), + Sub(usize, VoR), + Mul(usize, VoR), + Jump(VoR, VoR), +} + +impl Instruction { + fn new_set(reg: &str, vor: &str) -> Self { + Instruction::Set((reg.as_bytes()[0] - b'a') as usize, VoR::new(vor)) + } + + fn new_sub(reg: &str, vor: &str) -> Self { + Instruction::Sub((reg.as_bytes()[0] - b'a') as usize, VoR::new(vor)) + } + + fn new_mul(reg: &str, vor: &str) -> Self { + Instruction::Mul((reg.as_bytes()[0] - b'a') as usize, VoR::new(vor)) + } + + fn new_jnz(vor: &str, jmp: &str) -> Self { + Instruction::Jump(VoR::new(vor), VoR::new(jmp)) + } + + fn new(input: &str) -> Self { + let mut parts = input.split_whitespace(); + match parts.next().unwrap() { + "set" => Self::new_set(parts.next().unwrap(), parts.next().unwrap()), + "sub" => Self::new_sub(parts.next().unwrap(), parts.next().unwrap()), + "mul" => Self::new_mul(parts.next().unwrap(), parts.next().unwrap()), + "jnz" => Self::new_jnz(parts.next().unwrap(), parts.next().unwrap()), + _ => panic!("Invalid instruction: {}", input), + } + } +} + +struct Coprocessor { + registers: [i32; 8], + instructions: Vec, + pc: i16, + count_mul: i32, +} + +impl Coprocessor { + fn new(input: &str) -> Self { + let registers = [0; 8]; + let instructions = input.lines().map(Instruction::new).collect_vec(); + + Coprocessor { + registers, + instructions, + pc: 0, + count_mul: 0, + } + } + + fn value_of(&self, vor: &VoR) -> i32 { + match vor { + VoR::Value(value) => *value, + VoR::Register(value) => self.registers[*value], + } + } + + fn execute_once(&mut self) -> bool { + if self.pc < 0 || self.pc >= self.instructions.len() as i16 { + return false; + } + match &self.instructions[self.pc as usize] { + Instruction::Set(reg, vor) => self.registers[*reg] = self.value_of(vor), + Instruction::Sub(reg, vor) => self.registers[*reg] -= self.value_of(vor), + Instruction::Mul(reg, vor) => { + self.registers[*reg] *= self.value_of(vor); + self.count_mul += 1; + } + Instruction::Jump(vor, jmp) => { + if self.value_of(vor) != 0 { + self.pc = self.pc + self.value_of(jmp) as i16 - 1; + } + } + } + self.pc += 1; + true + } +} + +pub fn day_23_v1(input: impl Into) -> i32 { + let mut proco = Coprocessor::new(&input.into()); + while proco.execute_once() {} + proco.count_mul +} + +pub fn day_23_v2(input: impl Into) -> i32 { + let mut proco = Coprocessor::new(&input.into()); + proco.registers[0] = 1; + for _ in 0..10 { + proco.execute_once(); + } + let start = proco.registers[1]; + let end = proco.registers[2]; + + (start..(end + 1)) + .step_by(17) + .filter(|&n| { + for i in 2..n { + if n % i == 0 { + return true; + } + } + false + }) + .count() as i32 +} + +solvable!(day_23, day_23_v1, day_23_v2, i32); diff --git a/src/year_2017/day_24.rs b/src/year_2017/day_24.rs new file mode 100644 index 0000000..cd350e1 --- /dev/null +++ b/src/year_2017/day_24.rs @@ -0,0 +1,117 @@ +use itertools::Itertools; + +fn parse_input(input: &str) -> Vec<(u16, u16)> { + input + .lines() + .map(|line| { + line + .split('/') + .map(|n| n.parse::().unwrap()) + .collect_tuple::<(u16, u16)>() + .unwrap() + }) + .collect::>() +} + +fn sub_best_path_v1(need: u16, current: u16, parts: &[(u16, u16)]) -> u16 { + let mut best_path = current; + for (pidx, part) in parts.iter().enumerate() { + let next_need = if need == part.0 { + part.1 + } else if need == part.1 { + part.0 + } else { + continue; + }; + let mut next_parts = parts.to_owned(); + next_parts.remove(pidx); + let new_best = sub_best_path_v1(next_need, current + part.0 + part.1, &next_parts); + best_path = std::cmp::max(best_path, new_best); + } + best_path +} + +fn sub_best_path_v2(need: u16, len: u16, current: u16, parts: &[(u16, u16)]) -> (u16, u16) { + let mut best_path = (len, current); + for (pidx, part) in parts.iter().enumerate() { + let next_need = if need == part.0 { + part.1 + } else if need == part.1 { + part.0 + } else { + continue; + }; + let mut next_parts = parts.to_owned(); + next_parts.remove(pidx); + let new_best = sub_best_path_v2(next_need, len + 1, current + part.0 + part.1, &next_parts); + if new_best.0 > best_path.0 || new_best.0 == best_path.0 && new_best.1 > best_path.1 { + best_path = new_best; + } + } + best_path +} + +pub fn day_24_v1(input: impl Into) -> u16 { + let parts = parse_input(&input.into()); + let mut best_path = 0; + for (pidx, part) in parts.iter().enumerate() { + let next_needed = if part.0 == 0 { + part.1 + } else if part.1 == 0 { + part.0 + } else { + continue; + }; + let mut next_parts = parts.clone(); + next_parts.remove(pidx); + let next_best = sub_best_path_v1(next_needed, part.0 + part.1, &next_parts); + best_path = std::cmp::max(best_path, next_best); + } + best_path +} + +pub fn day_24_v2(input: impl Into) -> u16 { + let parts = parse_input(&input.into()); + let mut best_path = (0, 0); + for (pidx, part) in parts.iter().enumerate() { + let next_needed = if part.0 == 0 { + part.1 + } else if part.1 == 0 { + part.0 + } else { + continue; + }; + let mut next_parts = parts.clone(); + next_parts.remove(pidx); + let (new_len, new_best) = sub_best_path_v2(next_needed, 1, part.0 + part.1, &next_parts); + if new_len > best_path.0 || new_len == best_path.0 && new_best > best_path.1 { + best_path = (new_len, new_best); + } + } + best_path.1 +} +solvable!(day_24, day_24_v1, day_24_v2, u16); + +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE: &str = "0/2\n\ + 2/2\n\ + 2/3\n\ + 3/4\n\ + 3/5\n\ + 0/1\n\ + 10/1\n\ + 9/10"; + + #[test] + fn works_with_samples_v1() { + assert_eq!(day_24_v1(SAMPLE), 31); + } + + #[test] + fn works_with_samples_v2() { + assert_eq!(day_24_v2(SAMPLE), 19); + } +} diff --git a/src/year_2017/day_25.rs b/src/year_2017/day_25.rs new file mode 100644 index 0000000..e4e01a9 --- /dev/null +++ b/src/year_2017/day_25.rs @@ -0,0 +1,179 @@ +use std::collections::HashMap; + +use itertools::Itertools; + +#[derive(Clone, Copy)] +struct Execution { + write_value: bool, + direction: bool, + next_state: u8, +} + +impl Execution { + fn new(input: Vec<&str>) -> Self { + println!("EXEC: {:?}", input); + + assert_eq!(input.len(), 3); + let mut write_value = input[0].split_whitespace(); + assert_eq!(write_value.nth(1).unwrap(), "Write"); + let write_value = matches!(write_value.nth(2).unwrap(), "1."); + + let mut direction = input[1].split_whitespace(); + assert_eq!(direction.nth(1).unwrap(), "Move"); + let direction = matches!(direction.nth(4).unwrap(), "right."); + + let mut next_state = input[2].split_whitespace(); + assert_eq!(next_state.nth(1).unwrap(), "Continue"); + let next_state = next_state.nth(2).unwrap().as_bytes()[0] - b'A'; + + Execution { + write_value, + direction, + next_state, + } + } +} + +struct State { + false_exec: Execution, + true_exec: Execution, +} + +impl State { + fn new(input: Vec<&str>) -> Self { + assert!(input.len() >= 9); + let in_state = input[0]; + assert_eq!(in_state.split_whitespace().count(), 3); + + let false_state = input[1]; + assert_eq!(false_state.split_whitespace().count(), 6); + let false_exec = Execution::new(input[2..=4].to_vec()); + + let true_state = input[5]; + assert_eq!(true_state.split_whitespace().count(), 6); + let true_exec = Execution::new(input[6..=8].to_vec()); + + State { + false_exec, + true_exec, + } + } + + fn exec(&self, value: bool) -> Execution { + match value { + true => self.true_exec, + false => self.false_exec, + } + } +} + +struct StateMachine { + counter: u32, + stopper: u32, + current_state: u8, + states: Vec, + tape: HashMap, + index: i32, +} + +impl StateMachine { + fn new(input: &str) -> Self { + let mut lines = input.lines(); + let current_state: Vec<_> = lines + .next() + .unwrap() + .strip_suffix('.') + .unwrap() + .split_whitespace() + .collect(); + assert_eq!(current_state.len(), 4); + let current_state = current_state[3].as_bytes()[0] - b'A'; + let stopper: Vec<_> = lines.next().unwrap().split_whitespace().collect(); + assert_eq!(stopper.len(), 7); + let stopper = stopper[5].parse::().unwrap(); + lines.next(); + let states = lines + .chunks(10) + .into_iter() + .map(|chunk| State::new(chunk.collect_vec())) + .collect(); + + StateMachine { + counter: 0, + stopper, + states, + current_state, + tape: HashMap::new(), + index: 0i32, + } + } + + fn _execute(&mut self) { + let state = &self.states[self.current_state as usize]; + let entry = self.tape.entry(self.index).or_insert(false); + let exec = state.exec(*entry); + *entry = exec.write_value; + self.index = match exec.direction { + true => self.index + 1, + false => self.index - 1, + }; + self.current_state = exec.next_state; + self.counter += 1; + } + + fn run(&mut self) { + while self.counter < self.stopper { + self._execute(); + } + } +} + +impl ToString for StateMachine { + fn to_string(&self) -> String { + self + .tape + .iter() + .sorted() + .map(|(_idx, value)| if *value { "1" } else { "0" }) + .join(" ") + } +} + +pub fn day_25(input: impl Into) -> u64 { + let mut state_machine = StateMachine::new(&input.into()); + state_machine.run(); + state_machine.tape.values().filter(|v| **v).count() as u64 +} + +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE: &str = "Begin in state A.\n\ + Perform a diagnostic checksum after 6 steps.\n\ + \n\ + In state A:\n\ + If the current value is 0:\n\ + - Write the value 1.\n\ + - Move one slot to the right.\n\ + - Continue with state B.\n\ + If the current value is 1:\n\ + - Write the value 0.\n\ + - Move one slot to the left.\n\ + - Continue with state B.\n\ + \n\ + In state B:\n\ + If the current value is 0:\n\ + - Write the value 1.\n\ + - Move one slot to the left.\n\ + - Continue with state A.\n\ + If the current value is 1:\n\ + - Write the value 1.\n\ + - Move one slot to the right.\n\ + - Continue with state A."; + + #[test] + fn works_with_samples_v1() { + assert_eq!(day_25(SAMPLE), 3); + } +}