Skip to content

Commit a240bb7

Browse files
committed
Clean up day 23
1 parent 022bafc commit a240bb7

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed

data/examples/23.txt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
kh-tc
2+
qp-kh
3+
de-cg
4+
ka-co
5+
yn-aq
6+
qp-ub
7+
cg-tb
8+
vc-aq
9+
tb-ka
10+
wh-tc
11+
yn-cg
12+
kh-ub
13+
ta-co
14+
de-co
15+
tc-td
16+
tb-wq
17+
wh-td
18+
ta-ka
19+
td-qp
20+
aq-cg
21+
wq-ub
22+
ub-vc
23+
de-ta
24+
wq-aq
25+
wq-vc
26+
wh-yn
27+
ka-de
28+
kh-ta
29+
co-tc
30+
wh-qp
31+
tb-vc
32+
td-yn

src/bin/23.rs

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
advent_of_code::solution!(23);
2+
use itertools::Itertools;
3+
use std::collections::{HashMap, HashSet, VecDeque};
4+
5+
pub fn part_one(input: &str) -> Option<usize> {
6+
// Parse the input into a graph keeping track of vertices and edges.
7+
let (vertices, edges) = parse_input(input);
8+
9+
// For part 1, simply find all the cliques of size 3 and count the ones that
10+
// contain a "t" in one of the vertices.
11+
let cliques = find_cliques_n(&vertices, &edges, 3);
12+
13+
let res = cliques
14+
.iter()
15+
.filter(|clique| clique.iter().any(|v| v.starts_with('t')))
16+
.count();
17+
18+
Some(res)
19+
}
20+
21+
fn parse_input(input: &str) -> (HashSet<&str>, HashMap<&str, HashSet<&str>>) {
22+
input.lines().fold(
23+
(HashSet::new(), HashMap::new()),
24+
|(mut vertices, mut edges), line| {
25+
let (left, right) = line.split_once("-").unwrap();
26+
vertices.insert(left);
27+
vertices.insert(right);
28+
edges.entry(left).or_insert_with(HashSet::new).insert(right);
29+
edges.entry(right).or_insert_with(HashSet::new).insert(left);
30+
(vertices, edges)
31+
},
32+
)
33+
}
34+
35+
fn find_cliques_n<'a>(
36+
vertices: &HashSet<&'a str>,
37+
edges: &HashMap<&'a str, HashSet<&'a str>>,
38+
n: usize,
39+
) -> HashSet<Vec<&'a str>> {
40+
let mut cliques = HashSet::new();
41+
42+
// We basically test every vertex for a clique of size k. To do this, we
43+
// start by adding all of the vertices to a queue with a clique of just
44+
// itself.
45+
let mut queue = vertices
46+
.iter()
47+
.map(|&v| (v, vec![v]))
48+
.collect::<VecDeque<_>>();
49+
50+
while let Some((vertex, mut clique)) = queue.pop_front() {
51+
// If we have found a clique of size k, we add it to the set of cliques.
52+
if clique.len() == n {
53+
clique.sort();
54+
cliques.insert(clique.clone());
55+
continue;
56+
}
57+
58+
// Get a list of neighbors for the current vertex.
59+
let mut neighbors = edges.get(vertex).unwrap().clone();
60+
neighbors.retain(|neighbor| {
61+
clique
62+
.iter()
63+
.all(|v| edges.get(v).unwrap().contains(neighbor))
64+
});
65+
66+
// Add all of the valid neighbors to the queue with the current clique.
67+
for neighbor in neighbors {
68+
let mut new_clique = clique.clone();
69+
new_clique.push(neighbor);
70+
queue.push_back((neighbor, new_clique));
71+
}
72+
}
73+
74+
// Return all the cliques we found.
75+
cliques
76+
}
77+
78+
pub fn part_two(input: &str) -> Option<String> {
79+
let (vertices, edges) = parse_input(input);
80+
let largest_clique = bron_kerbosch(
81+
&HashSet::new(),
82+
&mut vertices.iter().cloned().collect(),
83+
&mut HashSet::new(),
84+
&edges,
85+
);
86+
87+
let mut largest_clique = largest_clique.iter().collect::<Vec<_>>();
88+
largest_clique.sort();
89+
90+
let res = largest_clique.iter().join(",");
91+
Some(res)
92+
}
93+
94+
/// Bron-Kerbosch algorithm is a recursive algorithm for finding all maximal cliques in an undirected graph.
95+
fn bron_kerbosch<'a>(
96+
r: &HashSet<&'a str>,
97+
p: &mut HashSet<&'a str>,
98+
x: &mut HashSet<&'a str>,
99+
edges: &HashMap<&'a str, HashSet<&'a str>>,
100+
) -> HashSet<&'a str> {
101+
// If we have checked all our potential vertices and have no excluded
102+
// vertices, we have found a clique. We check if the current clique is
103+
// larger than the largest one we have found so far and store it.
104+
if p.is_empty() && x.is_empty() {
105+
return r.clone();
106+
}
107+
108+
// Try adding each vertex in p to the current clique.
109+
let mut largest_clique = HashSet::new();
110+
for vertex in p.clone() {
111+
// Make a new r with the current vertex added.
112+
let mut r = r.clone();
113+
r.insert(vertex);
114+
115+
// Make a new p and x where their vertices are the intersection of the
116+
// current vertex's neighbors and the current p and x.
117+
let neighbors = edges.get(vertex).unwrap();
118+
let mut new_p = p.intersection(neighbors).cloned().collect();
119+
let mut new_x = x.intersection(neighbors).cloned().collect();
120+
121+
// Recurse with the new r, p, and x.
122+
let clique = bron_kerbosch(&r, &mut new_p, &mut new_x, edges);
123+
if clique.len() > largest_clique.len() {
124+
largest_clique = clique;
125+
}
126+
127+
// Move the current vertex from p to x for the next iteration.
128+
p.remove(vertex);
129+
x.insert(vertex);
130+
}
131+
132+
largest_clique
133+
}
134+
135+
#[cfg(test)]
136+
mod tests {
137+
use super::*;
138+
139+
#[test]
140+
fn test_part_one() {
141+
let result = part_one(&advent_of_code::template::read_file("examples", DAY));
142+
assert_eq!(result, Some(7));
143+
}
144+
145+
#[test]
146+
fn test_part_two() {
147+
let result = part_two(&advent_of_code::template::read_file("examples", DAY));
148+
assert_eq!(result, Some(String::from("co,de,ka,ta")));
149+
}
150+
}

0 commit comments

Comments
 (0)