Created
December 24, 2024 05:58
-
-
Save icub3d/b98fd7a660535c41a664f7b2c12d0d81 to your computer and use it in GitHub Desktop.
Advent of Code 2024 - Day 23
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::collections::{HashMap, HashSet, VecDeque}; | |
use itertools::Itertools; | |
fn main() { | |
let now = std::time::Instant::now(); | |
let input = include_str!("input.txt"); | |
// Parse the input into a graph keeping track of vertices and edges. | |
let (vertices, edges) = input.lines().fold( | |
(HashSet::new(), HashMap::new()), | |
|(mut vertices, mut edges), line| { | |
let (left, right) = line.split_once("-").unwrap(); | |
vertices.insert(left); | |
vertices.insert(right); | |
edges.entry(left).or_insert_with(HashSet::new).insert(right); | |
edges.entry(right).or_insert_with(HashSet::new).insert(left); | |
(vertices, edges) | |
}, | |
); | |
println!("parse: {:?}", now.elapsed()); | |
// For part 1, simply find all the cliques of size 3 and count the ones that | |
// contain a "t" in one of the vertices. | |
let now = std::time::Instant::now(); | |
let cliques = find_cliques_n(&vertices, &edges, 3); | |
let p1 = cliques | |
.iter() | |
.filter(|clique| clique.iter().find(|v| v.starts_with('t')).is_some()) | |
.count(); | |
println!("p1: {} ({:?})", p1, now.elapsed()); | |
// let now = std::time::Instant::now(); | |
// let cliques = find_cliques_n(&vertices, &edges, 10); | |
// println!("cliques-10: {} ({:?})", cliques.len(), now.elapsed()); | |
// Use bron_kerbosch to find the largest clique. | |
// https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm | |
let now = std::time::Instant::now(); | |
let largest_clique = bron_kerbosch( | |
&HashSet::new(), | |
&mut vertices.iter().cloned().collect(), | |
&mut HashSet::new(), | |
&edges, | |
); | |
// For part 2, the solution is to sort the vertices and return them as a | |
// comma-separated string. | |
let mut largest_clique = largest_clique.iter().collect::<Vec<_>>(); | |
largest_clique.sort(); | |
let p2 = largest_clique.iter().join(","); | |
println!("p2: {} ({:?})", p2, now.elapsed()); | |
} | |
fn find_cliques_n<'a>( | |
vertices: &HashSet<&'a str>, | |
edges: &HashMap<&'a str, HashSet<&'a str>>, | |
n: usize, | |
) -> HashSet<Vec<&'a str>> { | |
let mut cliques = HashSet::new(); | |
// We basically test every vertex for a clique of size k. To do this, we | |
// start by adding all of the vertices to a queue with a clique of just | |
// itself. | |
let mut queue = vertices | |
.into_iter() | |
.map(|&v| (v, vec![v])) | |
.collect::<VecDeque<_>>(); | |
while let Some((vertex, mut clique)) = queue.pop_front() { | |
// If we've found a click of size k, we add it to the set of cliques. We sort | |
// the clique to make sure we don't add duplicates. | |
if clique.len() == n { | |
clique.sort(); | |
cliques.insert(clique.clone()); | |
continue; | |
} | |
// Get a list of neighbors for the current vertex. We only want to | |
// consider neighbors that are connected to all of the vertices in the | |
// current clique. | |
let mut neighbors = edges.get(vertex).unwrap().clone(); | |
neighbors.retain(|neighbor| { | |
clique | |
.iter() | |
.all(|v| edges.get(v).unwrap().contains(neighbor)) | |
}); | |
// Add all of the valid neighbors to the queue with the current clique. | |
for neighbor in neighbors { | |
let mut new_clique = clique.clone(); | |
new_clique.push(neighbor); | |
queue.push_back((neighbor, new_clique)); | |
} | |
} | |
// Return all the cliques we found. | |
cliques | |
} | |
// Bron-Kerbosch algorithm is a recursive algorithm for finding all maximal cliques in an undirected graph. | |
// r is the set of vertices in the current clique. | |
// p is the set of vertices that can be added to the current clique. | |
// x is the set of vertices that cannot be added to the current clique. | |
fn bron_kerbosch<'a>( | |
r: &HashSet<&'a str>, | |
p: &mut HashSet<&'a str>, | |
x: &mut HashSet<&'a str>, | |
edges: &HashMap<&'a str, HashSet<&'a str>>, | |
) -> HashSet<&'a str> { | |
// If we have checked all our potential vertices and have no excluded | |
// vertices, we have found a clique. We check if the current clique is | |
// larger than the largest clique we have found so far and store it. | |
if p.is_empty() && x.is_empty() { | |
return r.clone(); | |
} | |
// Try adding each vertex in p to the current clique. | |
let mut largest_clique = HashSet::new(); | |
for vertex in p.clone() { | |
// Make a new r with the current vertex added. | |
let mut r = r.clone(); | |
r.insert(vertex); | |
// Make a new p and x where their vertices are the intersection of the | |
// current vertex's neighbors and the current p and x. | |
let neighbors = edges.get(vertex).unwrap(); | |
let mut new_p = p.intersection(neighbors).cloned().collect(); | |
let mut new_x = x.intersection(neighbors).cloned().collect(); | |
// Recurse with the new r, p, and x. | |
let clique = bron_kerbosch(&r, &mut new_p, &mut new_x, edges); | |
if clique.len() > largest_clique.len() { | |
largest_clique = clique; | |
} | |
// Move the current vertex from p to x for the next iteration. | |
p.remove(vertex); | |
x.insert(vertex); | |
} | |
// Return the largest clique we found. | |
largest_clique | |
} | |
#[allow(dead_code)] | |
fn mermaid(vertices: &HashSet<&str>, edges: &HashMap<&str, HashSet<&str>>) { | |
println!("graph TD;"); | |
for vertex in vertices { | |
println!(" {};", vertex); | |
} | |
for (from, tos) in edges { | |
for to in tos { | |
println!(" {} --> {};", from, to); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment