diff --git a/src/bin/day8.rs b/src/bin/day8.rs index bc36ec3..f96f690 100644 --- a/src/bin/day8.rs +++ b/src/bin/day8.rs @@ -1,18 +1,9 @@ -#![forbid(elided_lifetimes_in_paths)] +#![forbid(elided_lifetimes_in_paths, unsafe_code)] use aoc23::read; use chumsky::prelude::*; -use std::{ - collections::{HashMap, HashSet}, - sync::{ - atomic::{AtomicBool, Ordering}, - Mutex - }, - thread, - time::Duration -}; +use std::collections::{HashMap, HashSet, VecDeque}; -#[derive(Clone, Copy, Debug)] enum Instruction { Left, Right @@ -27,7 +18,7 @@ impl Instruction { } } -#[derive(Clone, Debug)] +#[derive(Debug)] struct Node { left: String, right: String @@ -51,7 +42,7 @@ impl Node { } } -#[derive(Clone, Debug)] +#[derive(Debug)] struct Network { nodes: HashMap } @@ -92,15 +83,93 @@ fn parser() -> impl Parser, Network), Error = Simple>> = Vec::new(); +#[derive(Copy, Clone, Debug)] +struct Ghost<'a> { + node: &'a str, + i: usize, + steps: u128 +} + +impl<'a> Ghost<'a> { + fn new(node: &'a str) -> Self { + Self { + node, + i: 0, + steps: 0 + } + } + + fn advance(&mut self, ghost: Ghost<'a>) { + self.node = ghost.node; + self.i = ghost.i; + self.steps += ghost.steps; + } +} fn main() -> anyhow::Result<()> { let (instructions, network) = read("inputs/day8.txt", parser())?; + // build a lookup map of left/right predecessors + let mut left_pred: HashMap<&str, Vec<&str>> = HashMap::new(); + let mut right_pred: HashMap<&str, Vec<&str>> = HashMap::new(); + for (node, next) in &network.nodes { + left_pred.entry(&next.left).or_default().push(node); + right_pred.entry(&next.right).or_default().push(node); + } + eprintln!("Built pred lookup map"); + + // build a lookup map from instruction position and node with the length to the next + // finish node + let mut dp: HashMap<(&str, usize), Ghost<'_>> = HashMap::new(); + let mut q: VecDeque<(&str, usize)> = VecDeque::new(); + for node in network + .nodes + .keys() + .map(|node| node.as_str()) + .filter(|node| node.ends_with('Z')) + { + for i in 0 .. instructions.len() { + dp.insert((node, i), Ghost { node, i, steps: 0 }); + q.push_back((node, i)); + } + } + while let Some((node, i)) = q.pop_front() { + let ghost = dp[&(node, i)]; + let i_pred = match i { + 0 => instructions.len() - 1, + i => i - 1 + }; + let pred = match instructions[i_pred] { + Instruction::Left => left_pred.get(node), + Instruction::Right => right_pred.get(node) + }; + for node_pred in pred.into_iter().flatten() { + let new = (*node_pred, i_pred); + #[allow(clippy::map_entry)] // lint is opinionated garbage + if !dp.contains_key(&new) { + dp.insert(new, Ghost { + node: ghost.node, + i: ghost.i, + steps: ghost.steps + 1 + }); + q.push_back(new); + if dp.len() % 1_000_000 == 0 { + dbg!(dp.len()); + } + } + } + if ghost.steps == 0 { + dp.remove(&(node, i)); + } + } + eprintln!("Built ghost lookup map"); + // for (key, value) in &dp { + // eprintln!("\t{key:?}\t = \t{value:?}"); + // } + // let mut curr_node = "AAA"; // let mut i = 0; - // let mut steps = 0; + // let mut steps = 0_u128; // while curr_node != "ZZZ" { // curr_node = match instructions[i] { // Instruction::Left => network.left(curr_node), @@ -111,84 +180,39 @@ fn main() -> anyhow::Result<()> { // } // println!("{steps}"); - let curr_nodes: HashSet<&str> = network + let mut ghosts: Vec> = network .nodes .keys() - .map(|node| node.as_str()) .filter(|node| node.ends_with('A')) + .map(|node| Ghost::new(node)) .collect(); - dbg!(curr_nodes.len()); - - for (thread_index, node) in curr_nodes - .iter() - .map(|node| String::from(*node)) - .enumerate() - { - unsafe { - RESULTS.push(Mutex::new(Vec::new())); + let mut progress = 0; + loop { + if ghosts[0].steps > 0 + && ghosts + .iter() + .skip(1) + .all(|ghost| ghost.steps == ghosts[0].steps) + { + println!("{}", ghosts[0].steps); + break; } - let network = network.clone(); - let instructions = instructions.clone(); - thread::spawn(move || { - let mut curr_node = node.as_str(); - let mut i = 0; - let mut steps = 0; - while !STOP.load(Ordering::Relaxed) { - curr_node = match instructions[i] { - Instruction::Left => network.left(curr_node), - Instruction::Right => network.right(curr_node) - }; - i = (i + 1) % instructions.len(); - steps += 1; - if steps % 1_000_000 == 0 { - dbg!(steps); - } - if curr_node.ends_with('Z') { - unsafe { - RESULTS[thread_index].lock().unwrap().push(steps); - } - } - } - }); - } - 'outer: loop { - thread::sleep(Duration::from_secs(1)); - unsafe { - let res0 = RESULTS[0].lock().unwrap(); - 'i: for i in 0 .. res0.len() { - 't: for t in 1 .. RESULTS.len() { - let rest = RESULTS[t].lock().unwrap(); - for j in 0 .. rest.len() { - if res0[i] == rest[j] { - continue 't; - } - } - continue 'i; - } - println!("{}", res0[i]); - STOP.store(true, Ordering::Relaxed); - break 'outer; - } - } - } - // let mut i = 0; - // let mut steps = 0; - // while curr_nodes.iter().any(|node| !node.ends_with('Z')) { - // curr_nodes = curr_nodes - // .into_iter() - // .map(|node| match instructions[i] { - // Instruction::Left => network.left(node), - // Instruction::Right => network.right(node) - // }) - // .collect(); - // i = (i + 1) % instructions.len(); - // steps += 1; - // if steps % 10000 == 0 { - // dbg!(steps); - // } - // } - // println!("{steps}"); + let (ghost_index, ghost) = ghosts + .iter() + .enumerate() + .min_by_key(|(_, ghost)| ghost.steps) + .unwrap(); + if ghost.steps - progress >= 1_000_000_000_000 { + progress = ghost.steps; + eprintln!("progress: {progress}"); + } + + // eprint!("{ghost:?}\t -> \t"); + let ghost = dp[&(ghost.node, ghost.i)]; + // eprintln!("{ghost:?}"); + ghosts[ghost_index].advance(ghost); + } Ok(()) }