diff --git a/.gitea/workflows/build_and_run.yml b/.gitea/workflows/build_and_run.yml index 6a96701..9981f77 100644 --- a/.gitea/workflows/build_and_run.yml +++ b/.gitea/workflows/build_and_run.yml @@ -11,7 +11,7 @@ jobs: name: Challenge for day strategy: matrix: - day_number: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22] + day_number: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] runs-on: rust-bookworm steps: - name: Check out repository code diff --git a/day23/Cargo.toml b/day23/Cargo.toml new file mode 100644 index 0000000..2f4996b --- /dev/null +++ b/day23/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "day23" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/day23/src/main.rs b/day23/src/main.rs new file mode 100644 index 0000000..8b216da --- /dev/null +++ b/day23/src/main.rs @@ -0,0 +1,94 @@ +use std::collections::{HashMap, HashSet}; +use std::{env, fs}; + +fn read_input(path: &str) -> String { + fs::read_to_string(path).expect("Cannot read file.") +} + +fn parse_input(input: &str) -> HashMap<&str, HashSet<&str>> { + let mut network = HashMap::new(); + for connection in input.lines().filter(|l| !l.is_empty()) { + let pair = connection.split_once('-').unwrap(); + network + .entry(pair.0) + .or_insert(HashSet::new()) + .insert(pair.1); + network + .entry(pair.1) + .or_insert(HashSet::new()) + .insert(pair.0); + } + + network +} + +fn part_1(network: &HashMap<&str, HashSet<&str>>) -> usize { + let mut triplets = HashSet::new(); + let mut seen = HashSet::new(); + for a in network.keys() { + for b in network.keys() { + let connections = network.get(a).unwrap(); + if a == b + || !connections.contains(b) + || !(a.starts_with("t") || b.starts_with("t")) + || seen.contains(&(a, b)) + || seen.contains(&(b, a)) + { + continue; + } + seen.insert((a, b)); + + for common in connections.intersection(network.get(b).unwrap()) { + let mut triplet = vec![a, b, common]; + triplet.sort(); + triplets.insert(triplet); + } + } + } + + triplets.len() +} + +fn bron_kerbosch<'a>( + r: HashSet<&'a str>, + mut p: HashSet<&'a str>, + network: &HashMap<&'a str, HashSet<&'a str>>, +) -> Vec<&'a str> { + if p.is_empty() { + return r.iter().copied().collect(); + } + + let mut clique = Vec::new(); + for computer in p.clone() { + let connections = network.get(computer).unwrap(); + let res = bron_kerbosch( + r.union(&HashSet::from([computer])).copied().collect(), + p.intersection(connections).copied().collect(), + network, + ); + if res.len() > clique.len() { + clique = res; + } + p.remove(computer); + } + + clique +} + +fn part_2(network: &HashMap<&str, HashSet<&str>>) -> String { + let mut best_clique = + bron_kerbosch(HashSet::new(), network.keys().copied().collect(), &network); + best_clique.sort(); + best_clique.join(",") +} + +fn main() { + let args: Vec = env::args().collect(); + for arg in args.iter().skip(1) { + let input = read_input(&arg); + let network = parse_input(&input); + println!("[{}]", &arg); + println!("\t[Part 1] => Answer is '{}'.", part_1(&network)); + println!("\t[Part 2] => Answer is '{}'.", part_2(&network)); + } +}