aoc/year2024/day23.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
//! # LAN Party
//!
//! This is the [Clique problem](https://en.wikipedia.org/wiki/Clique_problem). For part one we
//! find triangles (cliques of size 3) for each node by checking if there's an edge between any
//! distinct pair of neighbouring nodes.
//!
//! Part two asks to find the maximum clique, for which we could use the
//! [Bron–Kerbosch algorithm](https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm).
//! However the input has a specific structure that enables a simpler approach of finding the
//! largest *maximal* clique using a greedy algorithm. Nodes are arranged in clusters of 13 and
//! the maximum clique is size 14. This approach will not necessarily work for any general graph,
//! but will work for the inputs provided.
use crate::util::hash::*;
type Input = (FastMap<usize, Vec<usize>>, Vec<[bool; 676]>);
/// Convert each character pair `xy` to an index from 0..676 so that we can use much faster array
/// lookup instead of a `HashMap`.
pub fn parse(input: &str) -> Input {
let mut nodes = FastMap::with_capacity(1_000);
let mut edges = vec![[false; 676]; 676];
let to_index = |b: &[u8]| 26 * to_usize(b[0]) + to_usize(b[1]);
let empty = || Vec::with_capacity(16);
for edge in input.as_bytes().chunks(6) {
let from = to_index(&edge[..2]);
let to = to_index(&edge[3..]);
// Graph is undirected so add edges to both nodes.
nodes.entry(from).or_insert_with(empty).push(to);
nodes.entry(to).or_insert_with(empty).push(from);
// https://en.wikipedia.org/wiki/Adjacency_matrix
edges[from][to] = true;
edges[to][from] = true;
}
(nodes, edges)
}
pub fn part1(input: &Input) -> usize {
let (nodes, edges) = input;
let mut seen = [false; 676];
let mut triangles = 0;
// Only consider nodes starting with `t`.
for n1 in 494..520 {
if let Some(neighbours) = nodes.get(&n1) {
seen[n1] = true;
for (i, &n2) in neighbours.iter().enumerate() {
for &n3 in neighbours.iter().skip(i) {
// Skip nodes if we've already seen them.
if !seen[n2] && !seen[n3] && edges[n2][n3] {
triangles += 1;
}
}
}
}
}
triangles
}
pub fn part2(input: &Input) -> String {
let (nodes, edges) = input;
let mut seen = [false; 676];
let mut clique = Vec::new();
let mut largest = Vec::new();
// Greedy algorithm to find *maximal* (not maximum) cliques.
for (&n1, neighbours) in nodes {
if !seen[n1] {
clique.clear();
clique.push(n1);
// Add nodes if they're connected to every node already in the clique.
for &n2 in neighbours {
if clique.iter().all(|&c| edges[n2][c]) {
seen[n2] = true;
clique.push(n2);
}
}
// For the specific graphs given in the input
// finding the largest maximal clique will work.
if clique.len() > largest.len() {
largest.clone_from(&clique);
}
}
}
// Convert each index back into 2 character identifiers sorted alphabetically.
let mut result = String::new();
largest.sort_unstable();
for n in largest {
result.push(to_char(n / 26));
result.push(to_char(n % 26));
result.push(',');
}
result.pop();
result
}
fn to_usize(b: u8) -> usize {
(b - b'a') as usize
}
fn to_char(u: usize) -> char {
((u as u8) + b'a') as char
}