diff --git a/.aocf/cache/aoc2023_20.json b/.aocf/cache/aoc2023_20.json index a7bbaea..94b8b49 100644 --- a/.aocf/cache/aoc2023_20.json +++ b/.aocf/cache/aoc2023_20.json @@ -1,12 +1,14 @@ { "year": 2023, "day": 20, - "level": "first", + "level": "second", "title": "Pulse Propagation", - "stars": null, - "solution": {}, + "stars": 1, + "solution": { + "first": "886701120" + }, "input": "%jb -> ps\n%cm -> ps, tm\n%sl -> ml, cp\n%qr -> ml\n%hf -> kh, jg\n%jg -> kk\n%jt -> pq\n%qv -> kv\n%rj -> mm, kh\n%kf -> xt\n%kx -> vk, mk\n%dq -> qn\n&ps -> xc, mq, jt, zs, sr, nt, pq\n%jk -> hh, ps\n%rr -> mk, nh\n%hs -> kh, mb\n%mg -> mk, kf\n%xt -> dq, mk\n&xc -> zh\n%mq -> nt\n%nh -> bm\n&ml -> bp, gd, qv, kq\n%md -> hs\n%vk -> mk, vl\n%mm -> kh\n&th -> zh\n&zh -> rx\n%kc -> ps, jk\n%kk -> dm\n%jn -> ll, ml\n&pd -> zh\n&kh -> jg, qx, md, th, hf, dm, kk\n%pp -> kh, md\n%zf -> ml, bd\n%qx -> pp\n&mk -> kf, qn, nh, pd, dq, mg, bm\n%qn -> rr\n%mb -> qb, kh\n%nt -> jt\n%vl -> zk, mk\n%gd -> ml, rm\n%hh -> ps, jb\n%tm -> ps, mq\n%kv -> jn, ml\n%zs -> kc\n%ll -> ml, kq\n%cp -> qv, ml\n%rm -> sl, ml\n%bd -> qr, ml\n%dm -> qx\n%qb -> rj, kh\n%pq -> zs\n%bm -> kx\n%sr -> cm, ps\n%zk -> mk\nbroadcaster -> sr, gd, mg, hf\n%kq -> zf\n&bp -> zh\n", "brief": { - "first": "With your help, the Elves manage to find the right parts and fix all of the machines. Now, they just need to send the command to boot up the machines and get the sand flowing again.\n\nThe machines are far apart and wired together with long *cables*. The cables don't connect to the machines directly, but rather to communication *modules* attached to the machines that perform various initialization tasks and also act as communication relays.\n\nModules communicate using *pulses*. Each pulse is either a *high pulse* or a *low pulse*. When a module sends a pulse, it sends that type of pulse to each module in its list of *destination modules*.\n\nThere are several different types of modules:\n\n*Flip-flop* modules (prefix `%`) are either *on* or *off*; they are initially *off*. If a flip-flop module receives a high pulse, it is ignored and nothing happens. However, if a flip-flop module receives a low pulse, it *flips between on and off*. If it was off, it turns on and sends a high pulse. If it was on, it turns off and sends a low pulse.\n\n*Conjunction* modules (prefix `&`) *remember* the type of the most recent pulse received from *each* of their connected input modules; they initially default to remembering a *low pulse* for each input. When a pulse is received, the conjunction module first updates its memory for that input. Then, if it remembers *high pulses* for all inputs, it sends a *low pulse*; otherwise, it sends a *high pulse*.\n\nThere is a single *broadcast module* (named `broadcaster`). When it receives a pulse, it sends the same pulse to all of its destination modules.\n\nHere at Desert Machine Headquarters, there is a module with a single button on it called, aptly, the *button module*. When you push the button, a single *low pulse* is sent directly to the `broadcaster` module.\n\nAfter pushing the button, you must wait until all pulses have been delivered and fully handled before pushing it again. Never push the button if modules are still processing pulses.\n\nPulses are always processed *in the order they are sent*. So, if a pulse is sent to modules `a`, `b`, and `c`, and then module `a` processes its pulse and sends more pulses, the pulses sent to modules `b` and `c` would have to be handled first.\n\nThe module configuration (your puzzle input) lists each module. The name of the module is preceded by a symbol identifying its type, if any. The name is then followed by an arrow and a list of its destination modules. For example:\n\n```\nbroadcaster -> a, b, c\n%a -> b\n%b -> c\n%c -> inv\n&inv -> a\n\n```\n\nIn this module configuration, the broadcaster has three destination modules named `a`, `b`, and `c`. Each of these modules is a flip-flop module (as indicated by the `%` prefix). `a` outputs to `b` which outputs to `c` which outputs to another module named `inv`. `inv` is a conjunction module (as indicated by the `&` prefix) which, because it has only one input, acts like an inverter (it sends the opposite of the pulse type it receives); it outputs to `a`.\n\nBy pushing the button once, the following pulses are sent:\n\n```\nbutton -low-> broadcaster\nbroadcaster -low-> a\nbroadcaster -low-> b\nbroadcaster -low-> c\na -high-> b\nb -high-> c\nc -high-> inv\ninv -low-> a\na -low-> b\nb -low-> c\nc -low-> inv\ninv -high-> a\n\n```\n\nAfter this sequence, the flip-flop modules all end up *off*, so pushing the button again repeats the same sequence.\n\nHere's a more interesting example:\n\n```\nbroadcaster -> a\n%a -> inv, con\n&inv -> b\n%b -> con\n&con -> output\n\n```\n\nThis module configuration includes the `broadcaster`, two flip-flops (named `a` and `b`), a single-input conjunction module (`inv`), a multi-input conjunction module (`con`), and an untyped module named `output` (for testing purposes). The multi-input conjunction module `con` watches the two flip-flop modules and, if they're both on, sends a *low pulse* to the `output` module.\n\nHere's what happens if you push the button once:\n\n```\nbutton -low-> broadcaster\nbroadcaster -low-> a\na -high-> inv\na -high-> con\ninv -low-> b\ncon -high-> output\nb -high-> con\ncon -low-> output\n\n```\n\nBoth flip-flops turn on and a low pulse is sent to `output`! However, now that both flip-flops are on and `con` remembers a high pulse from each of its two inputs, pushing the button a second time does something different:\n\n```\nbutton -low-> broadcaster\nbroadcaster -low-> a\na -low-> inv\na -low-> con\ninv -high-> b\ncon -high-> output\n\n```\n\nFlip-flop `a` turns off! Now, `con` remembers a low pulse from module `a`, and so it sends only a high pulse to `output`.\n\nPush the button a third time:\n\n```\nbutton -low-> broadcaster\nbroadcaster -low-> a\na -high-> inv\na -high-> con\ninv -low-> b\ncon -low-> output\nb -low-> con\ncon -high-> output\n\n```\n\nThis time, flip-flop `a` turns on, then flip-flop `b` turns off. However, before `b` can turn off, the pulse sent to `con` is handled first, so it *briefly remembers all high pulses* for its inputs and sends a low pulse to `output`. After that, flip-flop `b` turns off, which causes `con` to update its state and send a high pulse to `output`.\n\nFinally, with `a` on and `b` off, push the button a fourth time:\n\n```\nbutton -low-> broadcaster\nbroadcaster -low-> a\na -low-> inv\na -low-> con\ninv -high-> b\ncon -high-> output\n\n```\n\nThis completes the cycle: `a` turns off, causing `con` to remember only low pulses and restoring all modules to their original states.\n\nTo get the cables warmed up, the Elves have pushed the button `1000` times. How many pulses got sent as a result (including the pulses sent by the button itself)?\n\nIn the first example, the same thing happens every time the button is pushed: `8` low pulses and `4` high pulses are sent. So, after pushing the button `1000` times, `8000` low pulses and `4000` high pulses are sent. Multiplying these together gives `*32000000*`.\n\nIn the second example, after pushing the button `1000` times, `4250` low pulses and `2750` high pulses are sent. Multiplying these together gives `*11687500*`.\n\nConsult your module configuration; determine the number of low pulses and high pulses that would be sent after pushing the button `1000` times, waiting for all pulses to be fully handled after each push of the button. *What do you get if you multiply the total number of low pulses sent by the total number of high pulses sent?*\n\nTo begin, [get your puzzle input](20/input).\n\nAnswer:" + "first": "With your help, the Elves manage to find the right parts and fix all of the machines. Now, they just need to send the command to boot up the machines and get the sand flowing again.\n\nThe machines are far apart and wired together with long *cables*. The cables don't connect to the machines directly, but rather to communication *modules* attached to the machines that perform various initialization tasks and also act as communication relays.\n\nModules communicate using *pulses*. Each pulse is either a *high pulse* or a *low pulse*. When a module sends a pulse, it sends that type of pulse to each module in its list of *destination modules*.\n\nThere are several different types of modules:\n\n*Flip-flop* modules (prefix `%`) are either *on* or *off*; they are initially *off*. If a flip-flop module receives a high pulse, it is ignored and nothing happens. However, if a flip-flop module receives a low pulse, it *flips between on and off*. If it was off, it turns on and sends a high pulse. If it was on, it turns off and sends a low pulse.\n\n*Conjunction* modules (prefix `&`) *remember* the type of the most recent pulse received from *each* of their connected input modules; they initially default to remembering a *low pulse* for each input. When a pulse is received, the conjunction module first updates its memory for that input. Then, if it remembers *high pulses* for all inputs, it sends a *low pulse*; otherwise, it sends a *high pulse*.\n\nThere is a single *broadcast module* (named `broadcaster`). When it receives a pulse, it sends the same pulse to all of its destination modules.\n\nHere at Desert Machine Headquarters, there is a module with a single button on it called, aptly, the *button module*. When you push the button, a single *low pulse* is sent directly to the `broadcaster` module.\n\nAfter pushing the button, you must wait until all pulses have been delivered and fully handled before pushing it again. Never push the button if modules are still processing pulses.\n\nPulses are always processed *in the order they are sent*. So, if a pulse is sent to modules `a`, `b`, and `c`, and then module `a` processes its pulse and sends more pulses, the pulses sent to modules `b` and `c` would have to be handled first.\n\nThe module configuration (your puzzle input) lists each module. The name of the module is preceded by a symbol identifying its type, if any. The name is then followed by an arrow and a list of its destination modules. For example:\n\n```\nbroadcaster -> a, b, c\n%a -> b\n%b -> c\n%c -> inv\n&inv -> a\n\n```\n\nIn this module configuration, the broadcaster has three destination modules named `a`, `b`, and `c`. Each of these modules is a flip-flop module (as indicated by the `%` prefix). `a` outputs to `b` which outputs to `c` which outputs to another module named `inv`. `inv` is a conjunction module (as indicated by the `&` prefix) which, because it has only one input, acts like an inverter (it sends the opposite of the pulse type it receives); it outputs to `a`.\n\nBy pushing the button once, the following pulses are sent:\n\n```\nbutton -low-> broadcaster\nbroadcaster -low-> a\nbroadcaster -low-> b\nbroadcaster -low-> c\na -high-> b\nb -high-> c\nc -high-> inv\ninv -low-> a\na -low-> b\nb -low-> c\nc -low-> inv\ninv -high-> a\n\n```\n\nAfter this sequence, the flip-flop modules all end up *off*, so pushing the button again repeats the same sequence.\n\nHere's a more interesting example:\n\n```\nbroadcaster -> a\n%a -> inv, con\n&inv -> b\n%b -> con\n&con -> output\n\n```\n\nThis module configuration includes the `broadcaster`, two flip-flops (named `a` and `b`), a single-input conjunction module (`inv`), a multi-input conjunction module (`con`), and an untyped module named `output` (for testing purposes). The multi-input conjunction module `con` watches the two flip-flop modules and, if they're both on, sends a *low pulse* to the `output` module.\n\nHere's what happens if you push the button once:\n\n```\nbutton -low-> broadcaster\nbroadcaster -low-> a\na -high-> inv\na -high-> con\ninv -low-> b\ncon -high-> output\nb -high-> con\ncon -low-> output\n\n```\n\nBoth flip-flops turn on and a low pulse is sent to `output`! However, now that both flip-flops are on and `con` remembers a high pulse from each of its two inputs, pushing the button a second time does something different:\n\n```\nbutton -low-> broadcaster\nbroadcaster -low-> a\na -low-> inv\na -low-> con\ninv -high-> b\ncon -high-> output\n\n```\n\nFlip-flop `a` turns off! Now, `con` remembers a low pulse from module `a`, and so it sends only a high pulse to `output`.\n\nPush the button a third time:\n\n```\nbutton -low-> broadcaster\nbroadcaster -low-> a\na -high-> inv\na -high-> con\ninv -low-> b\ncon -low-> output\nb -low-> con\ncon -high-> output\n\n```\n\nThis time, flip-flop `a` turns on, then flip-flop `b` turns off. However, before `b` can turn off, the pulse sent to `con` is handled first, so it *briefly remembers all high pulses* for its inputs and sends a low pulse to `output`. After that, flip-flop `b` turns off, which causes `con` to update its state and send a high pulse to `output`.\n\nFinally, with `a` on and `b` off, push the button a fourth time:\n\n```\nbutton -low-> broadcaster\nbroadcaster -low-> a\na -low-> inv\na -low-> con\ninv -high-> b\ncon -high-> output\n\n```\n\nThis completes the cycle: `a` turns off, causing `con` to remember only low pulses and restoring all modules to their original states.\n\nTo get the cables warmed up, the Elves have pushed the button `1000` times. How many pulses got sent as a result (including the pulses sent by the button itself)?\n\nIn the first example, the same thing happens every time the button is pushed: `8` low pulses and `4` high pulses are sent. So, after pushing the button `1000` times, `8000` low pulses and `4000` high pulses are sent. Multiplying these together gives `*32000000*`.\n\nIn the second example, after pushing the button `1000` times, `4250` low pulses and `2750` high pulses are sent. Multiplying these together gives `*11687500*`.\n\nConsult your module configuration; determine the number of low pulses and high pulses that would be sent after pushing the button `1000` times, waiting for all pulses to be fully handled after each push of the button. *What do you get if you multiply the total number of low pulses sent by the total number of high pulses sent?*\n\nYour puzzle answer was `886701120`.\n\nThe first half of this puzzle is complete! It provides one gold star: \\*\n\n\\--- Part Two ---\n----------\n\nThe final machine responsible for moving the sand down to Island Island has a module attached named `rx`. The machine turns on when a *single low pulse* is sent to `rx`.\n\nReset all modules to their default states. Waiting for all pulses to be fully handled after each button press, *what is the fewest number of button presses required to deliver a single low pulse to the module named `rx`?*\n\nAnswer:\n\nAlthough it hasn't changed, you can still [get your puzzle input](20/input)." } } \ No newline at end of file diff --git a/src/day20.rs b/src/day20.rs index e9e45f6..e987072 100644 --- a/src/day20.rs +++ b/src/day20.rs @@ -6,6 +6,7 @@ use problem::*; type Name = String; type Modules = HashMap; type Memory = HashMap; +static DEBUG: bool = false; #[derive(Copy, Clone, Debug)] enum State { @@ -51,7 +52,7 @@ impl ModuleKind { } } -#[derive(Clone)] +#[derive(Clone, Debug)] struct Module { kind: ModuleKind, name: Name, @@ -123,7 +124,6 @@ impl Module { } Broadcaster => system.enqueue_pulses(self.generate_pulses(pulse.kind)), Untyped => (), - _ => unreachable!("Type: {:?}", self.kind), } } } @@ -147,6 +147,7 @@ impl Debug for Pulse { } /// A System of wired up Modules that can send pulses to eachother +#[derive(Debug)] struct System { modules: Modules, pulses: Queue, @@ -199,15 +200,20 @@ impl System { /// Press the button and run the System, until all pulses have been handled, `times` times after each other fn press_button_repeatedly(&mut self, times: Int) { - for _ in 0..times { + for i in 0..times { + debug!(DEBUG, "Press button: {}", i); self.press_button(); + debug!( + DEBUG, + "(high: {}, low: {})\n", self.high_pulses, self.low_pulses + ); } } fn run_until_all_pulses_handled(&mut self) { while !self.pulses.is_empty() { let pulse = self.pulses.pop_front().unwrap(); - //dbg!(&pulse); + debug!(DEBUG, &pulse); let mut destination = self .modules @@ -224,6 +230,21 @@ impl System { self.modules.insert(destination.name.clone(), destination); } } + + /// Initialize all conjunctions by remembering a low pulse for each input + fn initialize_conjunctions(mut self) -> System { + let modules: Vec = self.modules.values().cloned().collect(); + for module in modules { + for destination in &module.destinations { + if let Some(dest_module) = self.modules.get_mut(destination) { + if let ModuleKind::Conjuction(memory) = &mut dest_module.kind { + memory.insert(module.name.clone(), PulseKind::Low); + } + } + } + } + self + } } struct DayTwenty {} @@ -231,27 +252,36 @@ struct DayTwenty {} impl Problem for DayTwenty { const YEAR: Year = 2023; const DAY: Day = 20; - const PART_ONE_EXAMPLE_EXPECTED: Answer = 11687500; - const PART_ONE_EXPECTED: Answer = 0; - const PART_TWO_EXAMPLE_EXPECTED: Answer = 0; + const PART_ONE_EXPECTED: Answer = 886701120; const PART_TWO_EXPECTED: Answer = 0; - fn example_input() -> ExampleInput { - //TODO: Split up examples, so I can have multiple - " - broadcaster -> a - %a -> inv, con - &inv -> b - %b -> con - &con -> output - " + define_examples! { + ( + " + broadcaster -> a, b, c + %a -> b + %b -> c + %c -> inv + &inv -> a + ", + Expect::PartOne(32000000), + ), + ( + " + broadcaster -> a + %a -> inv, con + &inv -> b + %b -> con + &con -> output + ", + Expect::PartOne(11687500), + ) } - fn solve_part_one(input: Input, _is_example: bool) -> Answer { - dbg!(InputLines::from(input.clone())); - let mut system = System::parse(input); + fn solve_part_one(input: Input, is_example: bool) -> Answer { + debug!(is_example, InputLines::from(input.clone())); + let mut system = System::parse(input).initialize_conjunctions(); system.press_button_repeatedly(1000); - dbg!((system.high_pulses, system.low_pulses)); system.high_pulses * system.low_pulses } @@ -261,19 +291,3 @@ impl Problem for DayTwenty { } run!(DayTwenty); - -/* -Expected: -button -low-> broadcaster -broadcaster -low-> a -broadcaster -low-> b -broadcaster -low-> c -a -high-> b -b -high-> c -c -high-> inv -inv -low-> a -a -low-> b -b -low-> c -c -low-> inv -inv -high-> a -*/ diff --git a/src/problem.rs b/src/problem.rs index a5330dd..a6e5e02 100644 --- a/src/problem.rs +++ b/src/problem.rs @@ -11,6 +11,7 @@ pub use nom::IResult; pub use std::collections::HashMap; pub use std::fmt::Debug; +use std::iter; pub use std::iter::once; pub use std::time::Instant; @@ -66,11 +67,8 @@ pub trait Problem { const DAY: Day; // Expected values for the inputs, will be tested - const PART_ONE_EXAMPLE_EXPECTED: Answer; const PART_ONE_EXPECTED: Answer; - const PART_TWO_EXAMPLE_EXPECTED: Answer; const PART_TWO_EXPECTED: Answer; - const RUN_EXAMPLE: bool = true; /// Solve AoC(`YEAR`, `DAY`) part one fn solve_part_one(input: Input, is_example: bool) -> Answer; @@ -78,12 +76,12 @@ pub trait Problem { /// Solve AoC(`YEAR`, `DAY`) part two fn solve_part_two(input: Input, is_example: bool) -> Answer; - /// The Advent of Code example input - fn example_input() -> ExampleInput; + /// Define Advent of Code examples + fn define_examples() -> Vec; /// Trim example_input, remove preceding spaces from all lines, remove first \n, keep empty lines intact - fn trimmed_example_input() -> Input { - Self::example_input() + fn trim_example_input(input: ExampleInput) -> Input { + input .lines() .map(|line| { if line.trim().is_empty() { @@ -93,22 +91,11 @@ pub trait Problem { } }) .skip(1) // Skip first - .take(Self::example_input().lines().count().saturating_sub(2)) // Skip last + .take(input.lines().count().saturating_sub(2)) // Skip last .collect::>() .join("\n") } - fn part_one_example() -> Answer { - let input = Self::trimmed_example_input(); - let solution = Self::solve_part_one(input, true); - test!( - Self::PART_ONE_EXAMPLE_EXPECTED, - solution, - "part_one_example" - ); - solution - } - fn part_one() -> Answer { let input = aoc::get(Self::YEAR, Self::DAY); let solution = Self::solve_part_one(input, false); @@ -116,26 +103,47 @@ pub trait Problem { solution } - fn part_two_example() -> Answer { - let input = Self::trimmed_example_input(); - let solution = Self::solve_part_two(input, true); - test!( - Self::PART_TWO_EXAMPLE_EXPECTED, - solution, - "part_two_example" - ); - solution - } - fn part_two() -> Answer { let input = aoc::get(Self::YEAR, Self::DAY); let solution = Self::solve_part_two(input, false); test!(Self::PART_TWO_EXPECTED, solution, "part_two"); solution } + + /// Run all given examples + fn run_examples() -> bool { + static NAME_ONE: &str = "example_part_one()"; + static NAME_TWO: &str = "example_part_two()"; + let format = |part: &str, i: usize| { + format!("{} [{}/{}]", part, i + 1, Self::define_examples().len(),) + }; + + for (i, example) in Self::define_examples().iter().enumerate() { + let input = Self::trim_example_input(example.input); + match example.expect { + Expect::One(one) => { + test!(one, Self::solve_part_one(input, true), format(NAME_ONE, i)); + } + Expect::Two(two) => { + test!(two, Self::solve_part_two(input, true), format(NAME_TWO, i)); + } + Expect::Both(one, two) => { + test!( + one, + Self::solve_part_one(input.clone(), true), + format(NAME_ONE, i) + ); + test!(two, Self::solve_part_two(input, true), format(NAME_TWO, i)); + } + Expect::Any => (), + } + } + true + } } /// Benchmark and run all parts of an Advent of Code problem +/// Also runs all examples specified inside the run_examples function or define_examples() macro #[macro_export] macro_rules! run { ($problem:ty) => { @@ -147,16 +155,11 @@ macro_rules! run { assert_impl_problem::<$problem>(); // Use the benchmark_functions macro to benchmark all parts - if <$problem>::RUN_EXAMPLE { - benchmark_functions!( - <$problem>::part_one_example, - <$problem>::part_one, - <$problem>::part_two_example, - <$problem>::part_two - ); - } else { - benchmark_functions!(<$problem>::part_one, <$problem>::part_two); - } + benchmark_functions!( + <$problem>::run_examples, + <$problem>::part_one, + <$problem>::part_two + ); } }; } @@ -170,3 +173,41 @@ pub trait Parse { pub fn parse_num(input: &str) -> IResult<&str, Int> { map_res(digit1, str::parse::)(input) } + +/// Advent of Code ExampleInput expectation for Problem part one, part two, or both +pub enum Expect { + PartOne(Answer), + PartTwo(Answer), + PartsOneAndTwo(Answer, Answer), + Any, +} + +/// Advent of Code ExampleInput and expectation +pub struct Example { + pub input: ExampleInput, + pub expect: Expect, +} + +/// Define Advent of Code Examples +#[macro_export] +macro_rules! define_examples { + ( + $( + ( + $input:expr, + $expect:expr, + ) + ),* $(,)? + ) => { + fn define_examples() -> Vec { + vec![ + $( + Example { + input: $input, + expect: $expect, + }, + )* + ] + } + }; +}