From 5d811a08b6450d7716151068b0660ee17f33fd01 Mon Sep 17 00:00:00 2001 From: Bert Peters Date: Mon, 25 Dec 2023 14:14:29 +0100 Subject: [PATCH] Implement 2023 day 25 --- 2023/Cargo.toml | 1 + 2023/src/day25.rs | 141 +++++++++++++++++++++++++++++++++++++++- 2023/src/samples/25.txt | 13 ++++ 3 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 2023/src/samples/25.txt diff --git a/2023/Cargo.toml b/2023/Cargo.toml index 50a2551..d869cbf 100644 --- a/2023/Cargo.toml +++ b/2023/Cargo.toml @@ -12,6 +12,7 @@ clap = { version = "4.4.8", features = ["derive"] } linked-hash-map = "0.5.6" nom = "7.1.3" num-integer = "0.1.45" +rand = "0.8.5" [dev-dependencies] criterion = "0.5.1" diff --git a/2023/src/day25.rs b/2023/src/day25.rs index 0479f15..353c189 100644 --- a/2023/src/day25.rs +++ b/2023/src/day25.rs @@ -1,3 +1,140 @@ -pub fn part1(_input: &[u8]) -> anyhow::Result { - anyhow::bail!("Not implemented") +use std::cmp::Reverse; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::collections::HashSet; +use std::collections::VecDeque; + +use nom::bytes::complete::tag; +use nom::bytes::complete::take_until; +use nom::bytes::complete::take_while1; +use nom::combinator::opt; +use nom::multi::fold_many1; +use nom::sequence::terminated; +use nom::IResult; +use rand::seq::SliceRandom; +use rand::thread_rng; + +use crate::common::minmax; +use crate::common::parse_input; + +type Node<'a> = &'a [u8]; +type Graph<'a> = HashMap, HashSet>>; + +fn parse_graph(mut i: &[u8]) -> IResult<&[u8], Graph> { + fn parse_line<'a>(i: &'a [u8], graph: &mut Graph<'a>) -> IResult<&'a [u8], ()> { + let (i, name) = terminated(take_until(":"), tag(": "))(i)?; + + terminated( + fold_many1( + terminated(take_while1(|c: u8| c.is_ascii_alphabetic()), opt(tag(" "))), + || (), + move |_, other| { + graph.entry(name).or_default().insert(other); + graph.entry(other).or_default().insert(name); + }, + ), + tag("\n"), + )(i) + } + let mut graph = HashMap::new(); + + while !i.is_empty() { + let (remain, _) = parse_line(i, &mut graph)?; + i = remain; + } + + Ok((i, graph)) +} + +fn find_path<'a>( + s: &'a [u8], + t: &'a [u8], + graph: &Graph<'a>, + counts: &mut HashMap<(&'a [u8], &'a [u8]), usize>, +) { + let mut todo = VecDeque::new(); + let mut prev = HashMap::new(); + todo.push_back(s); + prev.insert(s, s); + + 'outer: while let Some(node) = todo.pop_front() { + for &other in graph[node].iter() { + if let Entry::Vacant(entry) = prev.entry(other) { + entry.insert(node); + if other == t { + break 'outer; + } else { + todo.push_back(other); + } + } + } + } + + let mut cur = t; + while let Some(ancestor) = prev.get(cur).copied() { + if cur == ancestor { + return; + } + let (a, b) = minmax(ancestor, cur); + *counts.entry((a, b)).or_default() += 1; + cur = ancestor; + } + unreachable!("Should not get here"); +} + +fn component_size(s: &[u8], graph: &Graph) -> usize { + let mut visited = HashSet::new(); + visited.insert(s); + + let mut todo = Vec::new(); + todo.push(s); + + while let Some(node) = todo.pop() { + todo.extend(graph[node].iter().filter(|&&v| visited.insert(v))); + } + + visited.len() +} + +pub fn part1(input: &[u8]) -> anyhow::Result { + let mut graph = parse_input(input, parse_graph)?; + let nodes: Vec<_> = graph.keys().copied().collect(); + let mut rng = thread_rng(); + let mut counts = HashMap::new(); + + for _ in 0..1000 { + let mut it = nodes.choose_multiple(&mut rng, 2); + let first = *it.next().unwrap(); + let second = *it.next().unwrap(); + + find_path(first, second, &graph, &mut counts); + } + + let mut weighted_edges: Vec<_> = counts.into_iter().collect(); + weighted_edges.sort_unstable_by_key(|c| Reverse(c.1)); + + let copy = graph.clone(); + + for &((a, b), _) in &weighted_edges[..3] { + graph.get_mut(&a).unwrap().remove(&b); + graph.get_mut(&b).unwrap().remove(&a); + } + + assert_ne!(graph, copy); + + let first_size = component_size(nodes[0], &graph); + + Ok((first_size * (graph.len() - first_size)).to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE: &[u8] = include_bytes!("samples/25.txt"); + + #[test] + fn sample_part1() { + assert_eq!("54", part1(SAMPLE).unwrap()); + } } diff --git a/2023/src/samples/25.txt b/2023/src/samples/25.txt new file mode 100644 index 0000000..bbfda0b --- /dev/null +++ b/2023/src/samples/25.txt @@ -0,0 +1,13 @@ +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