diff --git a/.aoc_tiles/tiles/2025/11.png b/.aoc_tiles/tiles/2025/11.png index a3e9c63..cd962b6 100644 Binary files a/.aoc_tiles/tiles/2025/11.png and b/.aoc_tiles/tiles/2025/11.png differ diff --git a/README.md b/README.md index 3d75012..923f7eb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- 2025 - 19 ⭐ - Rust + 2025 - 20 ⭐ - Rust

diff --git a/src/day11.rs b/src/day11.rs index 308ea7a..2bf1e7d 100644 --- a/src/day11.rs +++ b/src/day11.rs @@ -1,27 +1,19 @@ use aoc_runner_derive::{aoc, aoc_generator}; -use cached::proc_macro::cached; -use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; -use itertools::Itertools; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; type Edges = HashMap>; #[aoc_generator(day11)] -fn parse(input: &str) -> (Edges, Edges) { +fn parse(input: &str) -> Edges { let mut edges: Edges = HashMap::new(); - let mut rev_edges: Edges = HashMap::new(); for l in input.lines() { let (k, rest) = l.split_once(": ").unwrap(); for v in rest.split_ascii_whitespace() { edges.entry(k.to_string()).or_default().push(v.to_string()); - rev_edges - .entry(v.to_string()) - .or_default() - .push(k.to_string()); } } - (edges, rev_edges) + edges } fn find_path(cur: &str, goal: &str, edges: &Edges) -> u64 { @@ -35,26 +27,72 @@ fn find_path(cur: &str, goal: &str, edges: &Edges) -> u64 { } } +fn mark_paths<'a>( + cur: &'a str, + edges: &'a Edges, + mut reachable: HashSet<&'a str>, +) -> HashSet<&'a str> { + reachable.insert(cur); + if let Some(nexts) = edges.get(cur) { + for n in nexts { + if !reachable.contains(&n.as_str()) { + // reachable.insert(n); + reachable = mark_paths(n, edges, reachable); + } + } + } + reachable +} + #[aoc(day11, part1)] -fn part1((fwd, rev): &(Edges, Edges)) -> u64 { - let mut count = 0u64; - let mut to_check = Vec::from_iter(rev["out"].iter()); - - let mut pb = ProgressBar::no_length() - .with_style( - ProgressStyle::with_template( - "[{elapsed_precise}/{eta_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {per_sec}", - ) - .unwrap(), - ) - .with_finish(indicatif::ProgressFinish::AndLeave); - +fn part1(fwd: &Edges) -> u64 { find_path("you", "out", fwd) } #[aoc(day11, part2)] -fn part2(input: &(Edges, Edges)) -> u64 { - 0 +fn part2(edges: &Edges) -> u64 { + let mut rev = Edges::from_iter(edges.keys().map(|k| (k.to_owned(), vec![]))); + for (from, tos) in edges { + for to in tos { + rev.entry(to.to_owned()).or_default().push(from.to_owned()); + } + } + let mut reachable_dac = mark_paths("dac", edges, HashSet::new()); + reachable_dac = mark_paths("dac", &rev, reachable_dac); + let mut reachable_fft = mark_paths("fft", edges, HashSet::new()); + reachable_fft = mark_paths("fft", &rev, reachable_fft); + + let reachable: HashSet<&str> = reachable_dac + .intersection(&reachable_fft) + .copied() + .collect(); + + let unreachable: HashSet<&str> = HashSet::from_iter(edges.keys().map(|k| k.as_str())) + .difference(&reachable) + .copied() + .collect(); + + let mut reachable_edges = edges.clone(); + + for k in &unreachable { + reachable_edges.remove(*k); + } + for (_k, v) in reachable_edges.iter_mut() { + for ur in &unreachable { + if let Some(idx) = v.iter().position(|s| s == ur) { + v.remove(idx); + } + } + } + + // This is a bit of a cheat from viewing the graph and realizing all paths are ordered this way. + // However, knowing that fact that we can partition in this way, we could use a shortest-path algo + // to determine which order fft and dac are in. + // + // The assumption holds on my data and the example, so I think it's probably true of all inputs. + find_path("svr", "fft", &reachable_edges) + * find_path("fft", "dac", &reachable_edges) + * find_path("dac", "out", &reachable_edges) } #[cfg(test)] @@ -74,6 +112,20 @@ ggg: out hhh: ccc fff iii iii: out"; + const EXAMPLE2: &str = "svr: aaa bbb +aaa: fft +fft: ccc +bbb: tty +tty: ccc +ccc: ddd eee +ddd: hub +hub: fff +eee: dac +dac: fff +fff: ggg hhh +ggg: out +hhh: out"; + #[rstest] #[case(EXAMPLE, 5)] fn part1_example(#[case] input: &str, #[case] expected: u64) { @@ -81,7 +133,7 @@ iii: out"; } #[rstest] - #[case(EXAMPLE, 0)] + #[case(EXAMPLE2, 2)] fn part2_example(#[case] input: &str, #[case] expected: u64) { assert_eq!(part2(&parse(input)), expected); }