|
| 1 | +//! [Day 23: LAN Party](https://adventofcode.com/2024/day/23) |
| 2 | +
|
| 3 | +use petgraph::graph::{NodeIndex, UnGraph}; |
| 4 | +use std::collections::{HashMap, HashSet}; |
| 5 | + |
| 6 | +// Bron-Kerbosch recursive algorithm to find cliques |
| 7 | +fn bron_kerbosch( |
| 8 | + graph: &UnGraph<(), ()>, |
| 9 | + r: &mut HashSet<NodeIndex>, |
| 10 | + p: &mut HashSet<NodeIndex>, |
| 11 | + x: &mut HashSet<NodeIndex>, |
| 12 | + cliques: &mut Vec<Vec<NodeIndex>>, |
| 13 | +) { |
| 14 | + if p.is_empty() && x.is_empty() { |
| 15 | + cliques.push(r.iter().copied().collect()); |
| 16 | + return; |
| 17 | + } |
| 18 | + |
| 19 | + let p_clone = p.clone(); |
| 20 | + for &v in &p_clone { |
| 21 | + let mut r_new = r.clone(); |
| 22 | + r_new.insert(v); |
| 23 | + |
| 24 | + let neighbors: HashSet<_> = graph.neighbors(v).collect(); |
| 25 | + let mut p_new: HashSet<NodeIndex> = p.intersection(&neighbors).copied().collect(); |
| 26 | + let mut x_new: HashSet<NodeIndex> = x.intersection(&neighbors).copied().collect(); |
| 27 | + |
| 28 | + bron_kerbosch(graph, &mut r_new, &mut p_new, &mut x_new, cliques); |
| 29 | + |
| 30 | + p.remove(&v); |
| 31 | + x.insert(v); |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +struct Puzzle { |
| 36 | + connections: Vec<(String, String)>, |
| 37 | +} |
| 38 | + |
| 39 | +impl Puzzle { |
| 40 | + fn new() -> Self { |
| 41 | + Self { |
| 42 | + connections: Vec::new(), |
| 43 | + } |
| 44 | + } |
| 45 | + |
| 46 | + /// Get the puzzle input. |
| 47 | + fn configure(&mut self, path: &str) { |
| 48 | + let data = std::fs::read_to_string(path).unwrap_or_else(|_| { |
| 49 | + eprintln!("cannot read input file {path}"); |
| 50 | + std::process::exit(1); |
| 51 | + }); |
| 52 | + |
| 53 | + for line in data.lines() { |
| 54 | + if let Some((from, to)) = line.split_once('-') { |
| 55 | + self.connections.push((from.to_string(), to.to_string())); |
| 56 | + } |
| 57 | + } |
| 58 | + } |
| 59 | + |
| 60 | + /// Solve part one. |
| 61 | + fn part1(&self) -> usize { |
| 62 | + let mut graph: HashMap<String, HashSet<String>> = HashMap::new(); |
| 63 | + let mut triangles: HashSet<[&String; 3]> = HashSet::new(); |
| 64 | + |
| 65 | + for (n1, n2) in &self.connections { |
| 66 | + graph |
| 67 | + .entry(n1.to_string()) |
| 68 | + .or_default() |
| 69 | + .insert(n2.to_string()); |
| 70 | + |
| 71 | + graph |
| 72 | + .entry(n2.to_string()) |
| 73 | + .or_default() |
| 74 | + .insert(n1.to_string()); |
| 75 | + } |
| 76 | + |
| 77 | + for (node, neighbors) in &graph { |
| 78 | + for n1 in neighbors { |
| 79 | + for n2 in neighbors { |
| 80 | + if n1 != n2 && graph[n1].contains(n2) { |
| 81 | + let mut triangle = [node, n1, n2]; |
| 82 | + triangle.sort_unstable(); |
| 83 | + |
| 84 | + triangles.insert(triangle); |
| 85 | + } |
| 86 | + } |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + triangles |
| 91 | + .iter() |
| 92 | + .filter(|triangle| triangle.iter().any(|node| node.starts_with('t'))) |
| 93 | + .count() |
| 94 | + } |
| 95 | + |
| 96 | + /// Solve part two. |
| 97 | + fn part2(&self) -> String { |
| 98 | + // type G = Graph<(), (), Undirected>; |
| 99 | + |
| 100 | + // let mut graph = G::new_undirected(); |
| 101 | + // let mut nodes = HashMap::new(); |
| 102 | + |
| 103 | + // for (n1, n2) in &self.connections { |
| 104 | + // let i1 = *nodes.entry(n1).or_insert_with(|| graph.add_node(())); |
| 105 | + // let i2 = *nodes.entry(n2).or_insert_with(|| graph.add_node(())); |
| 106 | + |
| 107 | + // graph.add_edge(i1, i2, ()); |
| 108 | + // } |
| 109 | + |
| 110 | + let mut graph = UnGraph::<(), ()>::new_undirected(); |
| 111 | + |
| 112 | + let mut nodes = HashMap::new(); |
| 113 | + |
| 114 | + for (n1, n2) in &self.connections { |
| 115 | + let i1 = *nodes |
| 116 | + .entry(n1.clone()) |
| 117 | + .or_insert_with(|| graph.add_node(())); |
| 118 | + let i2 = *nodes |
| 119 | + .entry(n2.clone()) |
| 120 | + .or_insert_with(|| graph.add_node(())); |
| 121 | + graph.add_edge(i1, i2, ()); |
| 122 | + } |
| 123 | + |
| 124 | + let mut node_names: HashMap<&NodeIndex, &str> = HashMap::new(); |
| 125 | + for (k, v) in &nodes { |
| 126 | + node_names.insert(v, k); |
| 127 | + } |
| 128 | + |
| 129 | + // find the largest clique |
| 130 | + let mut cliques = Vec::new(); |
| 131 | + let mut r = HashSet::new(); |
| 132 | + let mut p: HashSet<NodeIndex> = graph.node_indices().collect(); |
| 133 | + let mut x = HashSet::new(); |
| 134 | + |
| 135 | + bron_kerbosch(&graph, &mut r, &mut p, &mut x, &mut cliques); |
| 136 | + |
| 137 | + let largest_clique = cliques.into_iter().max_by_key(std::vec::Vec::len); |
| 138 | + |
| 139 | + if let Some(largest_clique) = largest_clique { |
| 140 | + let mut clique_names = largest_clique |
| 141 | + .iter() |
| 142 | + .map(|idx| node_names[idx]) |
| 143 | + .collect::<Vec<_>>(); |
| 144 | + clique_names.sort_unstable(); |
| 145 | + |
| 146 | + return clique_names.join(",").to_string(); |
| 147 | + } |
| 148 | + |
| 149 | + String::new() |
| 150 | + } |
| 151 | +} |
| 152 | + |
| 153 | +fn main() { |
| 154 | + let args = aoc::parse_args(); |
| 155 | + let mut puzzle = Puzzle::new(); |
| 156 | + puzzle.configure(args.path.as_str()); |
| 157 | + println!("{}", puzzle.part1()); |
| 158 | + println!("{}", puzzle.part2()); |
| 159 | +} |
| 160 | + |
| 161 | +/// Test from puzzle input |
| 162 | +#[cfg(test)] |
| 163 | +mod test { |
| 164 | + use super::*; |
| 165 | + |
| 166 | + #[test] |
| 167 | + fn test_part1() { |
| 168 | + let mut puzzle = Puzzle::new(); |
| 169 | + puzzle.configure("test.txt"); |
| 170 | + assert_eq!(puzzle.part1(), 7); |
| 171 | + } |
| 172 | + |
| 173 | + #[test] |
| 174 | + fn test_part2() { |
| 175 | + let mut puzzle = Puzzle::new(); |
| 176 | + puzzle.configure("test.txt"); |
| 177 | + assert_eq!(puzzle.part2(), "co,de,ka,ta"); |
| 178 | + } |
| 179 | +} |
0 commit comments