diff --git a/README.md b/README.md index d71e9019..b0940c24 100644 --- a/README.md +++ b/README.md @@ -29,4 +29,4 @@ Development occurs in language-specific directories: |[Day22.hs](hs/src/Day22.hs)|[Day22.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day22.kt)|[day22.py](py/aoc2023/day22.py)|[day22.rs](rs/src/day22.rs)| ||[Day23.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day23.kt)||[day23.rs](rs/src/day23.rs)| |[Day24.hs](hs/src/Day24.hs) ½|[Day24.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day24.kt) ½|[day24.py](py/aoc2023/day24.py) ½|[day24.rs](rs/src/day24.rs) ½| -|[Day25.hs](hs/src/Day25.hs)|||| +|[Day25.hs](hs/src/Day25.hs)|||[day25.rs](rs/src/day25.rs)| diff --git a/rs/benches/criterion.rs b/rs/benches/criterion.rs index 2ba156cc..583f03e2 100644 --- a/rs/benches/criterion.rs +++ b/rs/benches/criterion.rs @@ -1,6 +1,6 @@ use aoc2023::{ day1, day10, day11, day12, day13, day14, day15, day16, day17, day18, day19, day2, day20, day21, - day22, day23, day24, day3, day4, day5, day6, day7, day8, day9, + day22, day23, day24, day25, day3, day4, day5, day6, day7, day8, day9, }; use criterion::{black_box, Criterion}; use std::env; @@ -160,6 +160,11 @@ fn aoc2023_bench(c: &mut Criterion) -> io::Result<()> { g.bench_function("part 1", |b| b.iter(|| day24::part1(black_box(&data)))); g.finish(); + let data = get_day_input(25)?; + let mut g = c.benchmark_group("day 25"); + g.bench_function("part 1", |b| b.iter(|| day25::part1(black_box(&data)))); + g.finish(); + Ok(()) } diff --git a/rs/src/day25.rs b/rs/src/day25.rs new file mode 100644 index 00000000..a41183f2 --- /dev/null +++ b/rs/src/day25.rs @@ -0,0 +1,112 @@ +use std::collections::{HashMap, HashSet, VecDeque}; +use std::fmt::Debug; +use std::hash::Hash; + +fn cut(gr: &HashMap>, n: usize) -> Option { + if n == 0 { + let mut components = vec![]; + let mut keys = gr.keys().copied().collect::>(); + while let Some(start) = keys.iter().next().copied() { + keys.remove(&start); + let mut stack = vec![start]; + let mut n = 0; + while let Some(node) = stack.pop() { + n += 1; + let Some(next) = gr.get(&node) else { + continue; + }; + stack.extend(next.iter().copied().filter(|node| keys.remove(node))); + } + components.push(n); + } + #[cfg(debug_assertions)] + eprintln!("{:?}", &components); + return if components.len() > 1 { + Some(components.into_iter().product()) + } else { + None + }; + } + + let mut weights = HashMap::<_, usize>::new(); + for start in gr.keys().copied() { + let mut queue = VecDeque::from([(start, Vec::new())]); + let mut visited = HashSet::from([start]); + while let Some((node, mut path)) = queue.pop_front() { + path.iter().rev().fold(node, |node1, node2| { + *weights + .entry((node1.min(*node2), node1.max(*node2))) + .or_default() += 1; + *node2 + }); + path.push(node); + let Some(next) = gr.get(&node) else { + continue; + }; + queue.extend( + next.iter() + .copied() + .filter(|node| visited.insert(*node)) + .map(|node| (node, path.clone())), + ); + } + } + let mut weights = weights.into_iter().collect::>(); + weights.sort_by_key(|(_, n)| *n); + + for ((a, b), _) in weights.into_iter().rev() { + #[cfg(debug_assertions)] + eprintln!("({:?}, {:?})", &a, &b); + let mut gr = gr.clone(); + let r1 = gr.get_mut(&a).is_some_and(|next| next.remove(&b)); + let r2 = gr.get_mut(&b).is_some_and(|next| next.remove(&a)); + debug_assert!(r1 & r2); + if let Some(r) = cut(&gr, n - 1) { + return Some(r); + } + } + + None +} + +pub fn part1(data: &str) -> Option { + let mut gr = HashMap::new(); + for line in data.lines() { + let Some((src, rhs)) = line.split_once(':') else { + continue; + }; + for dst in rhs.split_whitespace() { + gr.entry(src).or_insert_with(HashSet::new).insert(dst); + gr.entry(dst).or_insert_with(HashSet::new).insert(src); + } + } + cut(&gr, 3) +} + +#[cfg(test)] +mod tests { + use super::*; + use indoc::indoc; + use pretty_assertions::assert_eq; + + static EXAMPLE: &str = indoc! {" + jqt: rhn xhk nvd + rsh: frs pzl lsr + xhk: hfx + cmg: qnr nvd lhk bvb + rhn: xhk bvb hfx + bvb: xhk hfx + pzl: lsr hfx nvd + qnr: nvd + ntq: jqt hfx bvb xhk + nvd: lhk + lsr: lhk + rzs: qnr cmg lsr rsh + frs: qnr lhk lsr + "}; + + #[test] + fn part1_examples() { + assert_eq!(Some(54), part1(EXAMPLE)); + } +} diff --git a/rs/src/lib.rs b/rs/src/lib.rs index 1d531241..b42792cc 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -15,6 +15,7 @@ pub mod day21; pub mod day22; pub mod day23; pub mod day24; +pub mod day25; pub mod day3; pub mod day4; pub mod day5; diff --git a/rs/src/main.rs b/rs/src/main.rs index 148dcbbf..aa35f724 100644 --- a/rs/src/main.rs +++ b/rs/src/main.rs @@ -1,6 +1,6 @@ use aoc2023::{ day1, day10, day11, day12, day13, day14, day15, day16, day17, day18, day19, day2, day20, day21, - day22, day23, day24, day3, day4, day5, day6, day7, day8, day9, + day22, day23, day24, day25, day3, day4, day5, day6, day7, day8, day9, }; use std::collections::HashSet; use std::env; @@ -210,5 +210,12 @@ fn main() -> io::Result<()> { println!(); } + if args.is_empty() || args.contains("25") { + println!("Day 25"); + let data = get_day_input(25)?; + println!("{:?}", day25::part1(&data).expect("error")); + println!(); + } + Ok(()) }