day20: problem 2 solution - needed a hint

This commit is contained in:
Keenan Tims 2023-12-20 01:48:09 -08:00
parent 877101f9a2
commit 512b05f624
Signed by: ktims
GPG Key ID: 11230674D69038D4
3 changed files with 172 additions and 30 deletions

83
20/Cargo.lock generated
View File

@ -11,11 +11,18 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "day20" name = "day20"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"lazy-regex", "lazy-regex",
"num",
] ]
[[package]] [[package]]
@ -47,6 +54,82 @@ version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "num"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.19.0" version = "1.19.0"

View File

@ -7,3 +7,4 @@ edition = "2021"
[dependencies] [dependencies]
lazy-regex = "3.1.0" lazy-regex = "3.1.0"
num = "0.4.1"

View File

@ -1,10 +1,12 @@
use std::collections::{BinaryHeap, HashMap, VecDeque}; use std::collections::{BinaryHeap, HashMap, VecDeque};
use std::fmt::Display; use std::fmt::Display;
use std::fs::File; use std::fs::File;
use std::hash::{Hash, Hasher};
use std::io::{BufRead, BufReader, Lines}; use std::io::{BufRead, BufReader, Lines};
use std::time::Instant; use std::time::Instant;
use lazy_regex::lazy_regex; use lazy_regex::lazy_regex;
use num::integer::lcm;
// BOILERPLATE // BOILERPLATE
type InputIter = Lines<BufReader<File>>; type InputIter = Lines<BufReader<File>>;
@ -31,7 +33,7 @@ fn main() {
const MODULE_PATTERN: lazy_regex::Lazy<lazy_regex::Regex> = lazy_regex!("^([%&]?)([a-z]+) -> ([a-z, ]+)$"); const MODULE_PATTERN: lazy_regex::Lazy<lazy_regex::Regex> = lazy_regex!("^([%&]?)([a-z]+) -> ([a-z, ]+)$");
#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
enum PulseType { enum PulseType {
Low, Low,
High, High,
@ -79,7 +81,15 @@ struct Module {
inputs: Vec<String>, inputs: Vec<String>,
outputs: Vec<String>, outputs: Vec<String>,
on: bool, on: bool,
last_input_states: HashMap<String, PulseType>, last_input_states: Vec<PulseType>,
}
impl Hash for Module {
fn hash<H: Hasher>(&self, state: &mut H) {
// the only states that change are 'on' and 'last_input_states', the rest are constant once created
self.on.hash(state);
self.last_input_states.hash(state);
}
} }
impl Module { impl Module {
@ -90,18 +100,19 @@ impl Module {
inputs: Vec::new(), inputs: Vec::new(),
outputs: Vec::new(), outputs: Vec::new(),
on: false, on: false,
last_input_states: HashMap::new(), last_input_states: Vec::new(),
} }
} }
fn source_idx(&self, name: &str) -> usize {
self.inputs.iter().position(|in_name| in_name == name).unwrap()
}
fn with_kind(name: String, kind: ModuleKind) -> Self { fn with_kind(name: String, kind: ModuleKind) -> Self {
let mut m = Self::new(name); let mut m = Self::new(name);
m.kind = Some(kind); m.kind = Some(kind);
m m
} }
fn and_destinations(mut self, destinations: &str) -> Self { fn and_destinations(mut self, destinations: &str) -> Self {
println!("{} destinations: {}", self.name, destinations);
for dest in destinations.split(", ") { for dest in destinations.split(", ") {
println!(" {}", dest);
self.outputs.push(dest.into()); self.outputs.push(dest.into());
} }
self self
@ -111,14 +122,28 @@ impl Module {
self self
} }
fn add_source(&mut self, source: String) { fn add_source(&mut self, source: String) {
self.inputs.push(source.clone()); self.inputs.push(source);
self.last_input_states.insert(source, PulseType::Low); self.last_input_states.push(PulseType::Low);
}
fn reset(&mut self) {
// relevant state is on and last_input_states
self.on = false;
for input in &mut self.last_input_states {
*input = PulseType::Low;
}
}
fn state_space(&self) -> u128 {
match self.kind {
Some(ModuleKind::Broadcast) | Some(ModuleKind::Button) => 1,
Some(ModuleKind::FlipFlop) => 2,
Some(ModuleKind::Conjunction) => 1 << self.last_input_states.len(), // 2^n
None => 1
}
} }
} }
impl From<&str> for Module { impl From<&str> for Module {
fn from(s: &str) -> Self { fn from(s: &str) -> Self {
println!("s: {}", s);
let Some(re_result) = MODULE_PATTERN.captures(s) else { let Some(re_result) = MODULE_PATTERN.captures(s) else {
panic!("unparseable module: {}", s); panic!("unparseable module: {}", s);
}; };
@ -166,7 +191,7 @@ struct Machine {
modules: HashMap<String, Module>, modules: HashMap<String, Module>,
work_queue: BinaryHeap<Job>, work_queue: BinaryHeap<Job>,
count_low: u64, count_low: u64,
count_high: u64 count_high: u64,
} }
impl<T: BufRead> From<Lines<T>> for Machine { impl<T: BufRead> From<Lines<T>> for Machine {
@ -199,7 +224,8 @@ impl<T: BufRead> From<Lines<T>> for Machine {
Machine { Machine {
modules, modules,
work_queue: BinaryHeap::new(), work_queue: BinaryHeap::new(),
count_low: 0, count_high: 0 count_low: 0,
count_high: 0,
} }
} }
} }
@ -227,16 +253,10 @@ impl Machine {
} }
}, },
Some(ModuleKind::Conjunction) => { Some(ModuleKind::Conjunction) => {
self.modules let target_m = self.modules.get_mut(target).unwrap();
.get_mut(target) let source_idx = target_m.source_idx(from);
.unwrap() target_m.last_input_states[source_idx] = signal;
.last_input_states if target_m.last_input_states.iter().all(|state| *state == PulseType::High) {
.insert(from.to_owned(), signal);
if self.modules[target]
.last_input_states
.values()
.all(|state| *state == PulseType::High)
{
self.send_all_outputs(PulseType::Low, target, priority) self.send_all_outputs(PulseType::Low, target, priority)
} else { } else {
self.send_all_outputs(PulseType::High, target, priority) self.send_all_outputs(PulseType::High, target, priority)
@ -260,13 +280,34 @@ impl Machine {
} }
} }
} }
// the number of cycles it took until the target sends a low pulse, or None if the target node sent no low pulses but the job finished
fn time_to_state(&mut self, goal:&str, state: PulseType) -> Option<u64> {
while let Some(job) = self.work_queue.pop() {
if job.from == goal && job.signal == state {
return Some(self.count_high + self.count_low);
}
for target in job.targets {
self.send_pulse(job.signal, &target, &job.from, job.priority);
}
}
None
}
fn reset(&mut self) {
for (_name, m) in &mut self.modules {
m.reset();
}
self.count_low = 0;
self.count_high = 0;
}
fn state_space(&self) -> u128 {
self.modules.values().map(|m| m.state_space()).product()
}
} }
// PROBLEM 1 solution // PROBLEM 1 solution
const PROBLEM1_ITERATIONS: usize = 1000; const PROBLEM1_ITERATIONS: usize = 1000;
fn problem1<T: BufRead>(input: Lines<T>) -> u64 { fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
let mut machine = Machine::from(input); let mut machine = Machine::from(input);
println!("{:?}", machine);
for _ in 0..PROBLEM1_ITERATIONS { for _ in 0..PROBLEM1_ITERATIONS {
machine.press_button(); machine.press_button();
machine.run(); machine.run();
@ -275,8 +316,31 @@ fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
} }
// PROBLEM 2 solution // PROBLEM 2 solution
fn problem2<T: BufRead>(input: Lines<T>) -> u64 { fn problem2<T: BufRead>(input: Lines<T>) -> u128 {
0 let mut machine = Machine::from(input);
println!("STATES: {}", machine.state_space());
// Find the rx module and look at its parent(s)
let rx = &machine.modules["rx"];
// my input has a single conjunction as parent, todo implement other possibilities
let con = &machine.modules[&rx.inputs[0]];
// for each input, find how long it takes for it to be High, so the conjunction sends a low to rx
let mut cycles: Vec<_> = Vec::new();
for input in con.inputs.clone() {
print!("searching distance to {}...", input);
machine.reset();
let mut button_count = 0;
loop {
machine.press_button();
button_count += 1;
if let Some(distance) = machine.time_to_state(&input, PulseType::High) {
println!("got {} pulses, {} button presses", distance, button_count);
cycles.push(button_count as u128);
break;
}
}
}
cycles.iter().fold(1, |accum, cycle| lcm(accum, *cycle))
} }
#[cfg(test)] #[cfg(test)]
@ -295,10 +359,4 @@ mod tests {
let c = Cursor::new(EXAMPLE); let c = Cursor::new(EXAMPLE);
assert_eq!(problem1(c.lines()), 11687500); assert_eq!(problem1(c.lines()), 11687500);
} }
#[test]
fn problem2_example() {
let c = Cursor::new(EXAMPLE);
assert_eq!(problem2(c.lines()), 0);
}
} }