day(20): add solution
Signed-off-by: Matej Focko <me@mfocko.xyz>
This commit is contained in:
parent
3c813855e3
commit
7bca4427f7
2 changed files with 232 additions and 0 deletions
5
samples/day20.txt
Normal file
5
samples/day20.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
broadcaster -> a
|
||||
%a -> inv, con
|
||||
&inv -> b
|
||||
%b -> con
|
||||
&con -> output
|
227
src/bin/day20.rs
Normal file
227
src/bin/day20.rs
Normal file
|
@ -0,0 +1,227 @@
|
|||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use aoc_2023::*;
|
||||
|
||||
type Output1 = usize;
|
||||
type Output2 = Output1;
|
||||
|
||||
lazy_static! {
|
||||
static ref GATE_REGEX: Regex = Regex::new(r"([%&]?)(.*) -> (.*)").unwrap();
|
||||
}
|
||||
|
||||
fn gate_hash(name: &str) -> u64 {
|
||||
let mut h = DefaultHasher::new();
|
||||
name.hash(&mut h);
|
||||
h.finish()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum GateState {
|
||||
Repeater,
|
||||
FlipFlop(bool),
|
||||
Nand(u32),
|
||||
}
|
||||
|
||||
impl FromStr for GateState {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
match s {
|
||||
"" => Ok(GateState::Repeater),
|
||||
"%" => Ok(GateState::FlipFlop(false)),
|
||||
"&" => Ok(GateState::Nand(0)),
|
||||
_ => Err("Invalid kind"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GateState {
|
||||
fn exec(
|
||||
&mut self,
|
||||
watch: Option<u64>,
|
||||
src: u64,
|
||||
dest: &Gate,
|
||||
p: bool,
|
||||
) -> Option<(Option<usize>, bool)> {
|
||||
match self {
|
||||
GateState::Repeater => Some((None, p)),
|
||||
GateState::FlipFlop(b) => {
|
||||
if p {
|
||||
None
|
||||
} else {
|
||||
*b = !*b;
|
||||
Some((None, *b))
|
||||
}
|
||||
}
|
||||
GateState::Nand(v) => {
|
||||
let idx = dest
|
||||
.inputs
|
||||
.iter()
|
||||
.position(|i| gate_hash(i) == src)
|
||||
.unwrap();
|
||||
|
||||
let mut high = None;
|
||||
if p {
|
||||
if watch.is_some_and(|h| dest.hash == h) {
|
||||
high = Some(idx);
|
||||
}
|
||||
|
||||
*v |= 1 << idx;
|
||||
} else {
|
||||
*v &= !(1 << idx);
|
||||
}
|
||||
|
||||
Some((high, *v != (1 << dest.inputs.len()) - 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct Gate {
|
||||
inputs: Vec<String>,
|
||||
outputs: Vec<String>,
|
||||
hash: u64,
|
||||
}
|
||||
|
||||
impl Gate {
|
||||
fn new(name: &str) -> Gate {
|
||||
Gate {
|
||||
inputs: vec![],
|
||||
outputs: vec![],
|
||||
hash: gate_hash(name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Day20 {
|
||||
gates: HashMap<String, Gate>,
|
||||
initial_state: HashMap<String, GateState>,
|
||||
}
|
||||
|
||||
impl Day20 {
|
||||
fn push(
|
||||
&self,
|
||||
state: &mut HashMap<String, GateState>,
|
||||
watch: Option<u64>,
|
||||
) -> (usize, usize, Option<usize>) {
|
||||
let mut q = VecDeque::new();
|
||||
let mut counters = vec![0, 0];
|
||||
let mut high_idx = None;
|
||||
|
||||
q.push_back(("button".to_owned(), "broadcaster".to_owned(), false));
|
||||
while let Some((src, dest, pulse)) = q.pop_front() {
|
||||
counters[pulse as usize] += 1;
|
||||
|
||||
let (Some(g), Some(gs)) = (self.gates.get(&dest), state.get_mut(&dest)) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some((found_idx, next_pulse)) = gs.exec(watch, gate_hash(&src), g, pulse) {
|
||||
if found_idx.is_some() {
|
||||
high_idx = found_idx;
|
||||
}
|
||||
|
||||
for next_gate in &g.outputs {
|
||||
q.push_back((dest.clone(), next_gate.clone(), next_pulse));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(counters[0], counters[1], high_idx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Solution<Output1, Output2> for Day20 {
|
||||
fn new<P: AsRef<Path>>(pathname: P) -> Self {
|
||||
let lines: Vec<String> = file_to_lines(pathname);
|
||||
|
||||
let mut gates: HashMap<String, Gate> = HashMap::new();
|
||||
let mut initial_state: HashMap<String, GateState> = HashMap::new();
|
||||
|
||||
for (kind, name, outputs) in lines.into_iter().map(|g| {
|
||||
let (_, [kind, name, outputs]) = GATE_REGEX.captures(&g).unwrap().extract();
|
||||
(
|
||||
kind.to_owned(),
|
||||
name.to_owned(),
|
||||
outputs.split(", ").map(str::to_owned).collect_vec(),
|
||||
)
|
||||
}) {
|
||||
for o in &outputs {
|
||||
gates
|
||||
.entry(o.clone())
|
||||
.or_default()
|
||||
.inputs
|
||||
.push(name.clone());
|
||||
}
|
||||
|
||||
gates
|
||||
.entry(name.clone())
|
||||
.or_insert(Gate::new(&name))
|
||||
.outputs = outputs;
|
||||
initial_state.insert(name, kind.parse().unwrap());
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
gates
|
||||
.keys()
|
||||
.map(|k| gate_hash(k))
|
||||
.collect::<HashSet<u64>>()
|
||||
.len(),
|
||||
gates.len(),
|
||||
"no collisions in hash"
|
||||
);
|
||||
|
||||
Self {
|
||||
gates,
|
||||
initial_state,
|
||||
}
|
||||
}
|
||||
|
||||
fn part_1(&mut self) -> Output1 {
|
||||
let mut state = self.initial_state.clone();
|
||||
let (mut l, mut h) = (0, 0);
|
||||
|
||||
for _ in 0..1000 {
|
||||
let (l_iter, h_iter, _) = self.push(&mut state, None);
|
||||
l += l_iter;
|
||||
h += h_iter;
|
||||
}
|
||||
|
||||
l * h
|
||||
}
|
||||
|
||||
fn part_2(&mut self) -> Output2 {
|
||||
let rx_parent = &self.gates["rx"].inputs[0];
|
||||
let rx_parent_hash = Some(gate_hash(&rx_parent));
|
||||
|
||||
let mut cycles = vec![0_usize; self.gates[rx_parent].inputs.len()];
|
||||
let mut found = 0;
|
||||
|
||||
let mut state = self.initial_state.clone();
|
||||
for i in 1.. {
|
||||
if found >= cycles.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
if let (_, _, Some(idx)) = self.push(&mut state, rx_parent_hash) {
|
||||
if cycles[idx] == 0 {
|
||||
cycles[idx] = i;
|
||||
found += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cycles.into_iter().product1().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
Day20::main()
|
||||
}
|
||||
|
||||
test_sample!(day_20, Day20, 11687500, 0);
|
Loading…
Reference in a new issue