diff --git a/README.md b/README.md index 7b43317..f8eaf11 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Thank you to [Eric Wastl](http://was.tl) for running this incredible yearly even - [Day 20: Race Condition](aoc_2024/src/day_20.rs) - [Day 21: Keypad Conundrum](aoc_2024/src/day_21.rs) - [Day 22: Monkey Market](aoc_2024/src/day_22.rs) +- [Day 23: LAN Party](aoc_2024/src/day_23.rs) ## [2023](https://adventofcode.com/2023) [![aoc_2023](https://github.com/connorslade/advent-of-code/actions/workflows/aoc_2023.yml/badge.svg)](https://github.com/connorslade/advent-of-code/actions/workflows/aoc_2023.yml) diff --git a/aoc_2024/src/day_23.rs b/aoc_2024/src/day_23.rs new file mode 100644 index 0000000..aae33c1 --- /dev/null +++ b/aoc_2024/src/day_23.rs @@ -0,0 +1,145 @@ +use std::collections::{HashMap, HashSet}; + +use common::{solution, Answer}; +use itertools::Itertools; + +solution!("LAN Party", 23); + +fn part_a(input: &str) -> Answer { + let graph = parse(input); + + let mut triplets = HashSet::new(); + for key in graph.keys() { + let neighbors = &graph[key]; + + for x in neighbors { + for y in neighbors.iter().skip(1) { + if graph[x].contains(y) { + let mut sorted = vec![key, x, y]; + sorted.sort(); + triplets.insert((sorted[0], sorted[1], sorted[2])); + } + } + } + } + + triplets + .iter() + .filter(|(a, b, c)| a.starts_with('t') || b.starts_with('t') || c.starts_with('t')) + .count() + .into() +} + +fn part_b(input: &str) -> Answer { + let graph = parse(input); + + let mut cliques = Vec::new(); + bron_kerbosch( + &graph, + &mut HashSet::new(), + &mut graph.keys().cloned().collect(), + &mut HashSet::new(), + &mut cliques, + ); + + let max = cliques.iter().max_by_key(|x| x.len()).unwrap(); + max.iter().sorted().join(",").into() +} + +fn parse(input: &str) -> HashMap<&str, HashSet<&str>> { + let mut out: HashMap<&str, HashSet<&str>> = HashMap::new(); + + for line in input.lines() { + let (a, b) = line.split_once('-').unwrap(); + out.entry(a).or_default().insert(b); + out.entry(b).or_default().insert(a); + } + + out +} + +fn bron_kerbosch<'a>( + graph: &HashMap<&'a str, HashSet<&'a str>>, + r: &mut HashSet<&'a str>, + p: &mut HashSet<&'a str>, + x: &mut HashSet<&'a str>, + cliques: &mut Vec>, +) { + if p.is_empty() && x.is_empty() { + cliques.push(r.clone()); + return; + } + + let pivot = p.iter().chain(x.iter()).next().cloned(); + + for &v in p.clone().difference(&pivot.map_or_else(HashSet::new, |p| { + graph.get(&p).unwrap_or(&HashSet::new()).clone() + })) { + let mut r = r.clone(); + r.insert(v); + + let mut p = p + .intersection(graph.get(&v).unwrap_or(&HashSet::new())) + .cloned() + .collect(); + let mut x = x + .intersection(graph.get(&v).unwrap_or(&HashSet::new())) + .cloned() + .collect(); + + bron_kerbosch(graph, &mut r, &mut p, &mut x, cliques); + + p.remove(&v); + x.insert(v); + } +} + +#[cfg(test)] +mod test { + use indoc::indoc; + + const CASE: &str = indoc! {" + kh-tc + qp-kh + de-cg + ka-co + yn-aq + qp-ub + cg-tb + vc-aq + tb-ka + wh-tc + yn-cg + kh-ub + ta-co + de-co + tc-td + tb-wq + wh-td + ta-ka + td-qp + aq-cg + wq-ub + ub-vc + de-ta + wq-aq + wq-vc + wh-yn + ka-de + kh-ta + co-tc + wh-qp + tb-vc + td-yn + "}; + + #[test] + fn part_a() { + assert_eq!(super::part_a(CASE), 7.into()); + } + + #[test] + fn part_b() { + assert_eq!(super::part_b(CASE), "co,de,ka,ta".into()); + } +} diff --git a/aoc_2024/src/lib.rs b/aoc_2024/src/lib.rs index 24ea89f..9867a5e 100644 --- a/aoc_2024/src/lib.rs +++ b/aoc_2024/src/lib.rs @@ -22,6 +22,7 @@ mod day_19; mod day_20; mod day_21; mod day_22; +mod day_23; // [import_marker] pub const SOLUTIONS: &[Solution] = &[ @@ -47,5 +48,6 @@ pub const SOLUTIONS: &[Solution] = &[ day_20::SOLUTION, day_21::SOLUTION, day_22::SOLUTION, + day_23::SOLUTION, // [list_marker] ];