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