Skip to content

Commit bce98bd

Browse files
committed
Optimize 2024 day 23
and generalize to allow solving the example input
1 parent d8898b4 commit bce98bd

File tree

2 files changed

+118
-71
lines changed

2 files changed

+118
-71
lines changed
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

crates/year2024/src/day23.rs

Lines changed: 86 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,68 @@
1+
use utils::bit::BitIterator;
12
use utils::prelude::*;
23

34
/// Finding the largest clique in a graph.
5+
///
6+
/// Assumes each node has the same degree N, and the largest clique contains N nodes.
47
#[derive(Clone, Debug)]
58
pub struct Day23 {
6-
nodes: Vec<Node>,
7-
}
8-
9-
#[derive(Clone, Debug)]
10-
struct Node {
11-
name: [u8; 2],
12-
edges: Vec<usize>,
9+
nodes: [[u64; (26usize * 26).div_ceil(64)]; 26 * 26],
10+
degree: u32,
1311
}
1412

1513
impl Day23 {
1614
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
17-
let mut indexes = [None; 26 * 26];
18-
let mut nodes = vec![];
15+
let mut nodes = [[0u64; 11]; 676];
1916

2017
for item in parser::byte_range(b'a'..=b'z')
21-
.repeat_n(parser::noop())
22-
.repeat_n(b'-')
18+
.repeat_n::<2, _>(parser::noop())
19+
.repeat_n::<2, _>(b'-')
2320
.with_suffix(parser::eol())
2421
.parse_iterator(input)
2522
{
2623
let [n1, n2] = item?;
2724

28-
let mut index_of = |n: [u8; 2]| {
29-
let i = 26 * (n[0] - b'a') as usize + (n[1] - b'a') as usize;
30-
match indexes[i] {
31-
Some(index) => index,
32-
None => {
33-
let index = nodes.len();
34-
nodes.push(Node {
35-
name: n,
36-
edges: Vec::new(),
37-
});
38-
indexes[i] = Some(index);
39-
index
40-
}
41-
}
42-
};
25+
let index1 = 26 * (n1[0] - b'a') as usize + (n1[1] - b'a') as usize;
26+
let index2 = 26 * (n2[0] - b'a') as usize + (n2[1] - b'a') as usize;
27+
28+
nodes[index1][index2 / 64] |= 1 << (index2 % 64);
29+
nodes[index2][index1 / 64] |= 1 << (index1 % 64);
30+
}
4331

44-
let index1 = index_of(n1);
45-
let index2 = index_of(n2);
32+
let Some(first_node) = nodes.iter().find(|&b| b.iter().any(|&n| n != 0)) else {
33+
return Err(InputError::new(input, 0, "expected non-empty graph"));
34+
};
4635

47-
nodes[index1].edges.push(index2);
48-
nodes[index2].edges.push(index1);
36+
let degree = first_node.iter().map(|&n| n.count_ones()).sum::<u32>();
37+
if nodes.iter().any(|&b| {
38+
let d = b.iter().map(|&n| n.count_ones()).sum::<u32>();
39+
d != 0 && d != degree
40+
}) {
41+
return Err(InputError::new(
42+
input,
43+
0,
44+
"expected all nodes to have same degree",
45+
));
4946
}
5047

51-
Ok(Self { nodes })
48+
Ok(Self { nodes, degree })
5249
}
5350

5451
#[must_use]
5552
pub fn part1(&self) -> u32 {
5653
let mut count = 0;
57-
for (i1, n1) in self.nodes.iter().enumerate() {
58-
for (i2, n2) in n1.edges.iter().map(|&i| (i, &self.nodes[i])) {
59-
if i1 > i2 {
60-
continue;
54+
for n1 in 0..self.nodes.len() {
55+
for n2 in self.neighbors(n1) {
56+
if n2 > n1 {
57+
break;
6158
}
62-
for (i3, n3) in n2.edges.iter().map(|&i| (i, &self.nodes[i])) {
63-
if i2 < i3
64-
&& n1.edges.contains(&i3)
65-
&& (n1.name[0] == b't' || n2.name[0] == b't' || n3.name[0] == b't')
66-
{
59+
for n3 in Self::iter(Self::intersect(self.nodes[n1], self.nodes[n2])) {
60+
if n3 > n2 {
61+
break;
62+
}
63+
64+
// 19 = b't' - b'a'
65+
if n1 / 26 == 19 || n2 / 26 == 19 || n3 / 26 == 19 {
6766
count += 1;
6867
}
6968
}
@@ -74,46 +73,62 @@ impl Day23 {
7473

7574
#[must_use]
7675
pub fn part2(&self) -> String {
77-
let mut assigned = vec![false; self.nodes.len()];
78-
let mut node_lists = Vec::with_capacity(self.nodes.len());
7976
for i in 0..self.nodes.len() {
80-
if !assigned[i] {
81-
let c = node_lists.len();
82-
node_lists.push(Vec::new());
83-
self.try_add(i, &mut assigned, &mut node_lists[c]);
84-
}
85-
}
77+
'sets: for skip in self.neighbors(i) {
78+
if skip > i {
79+
break;
80+
}
8681

87-
let mut nodes = node_lists.into_iter().max_by_key(|l| l.len()).unwrap();
88-
nodes.sort_unstable_by_key(|&n| self.nodes[n].name);
89-
nodes
90-
.iter()
91-
.fold(String::with_capacity(nodes.len() * 3), |mut acc, &i| {
92-
if !acc.is_empty() {
93-
acc.push(',');
82+
// Set of N nodes is (neighbours + starting node - skipped neighbour)
83+
let mut connected = self.nodes[i];
84+
connected[i / 64] |= 1 << (i % 64);
85+
connected[skip / 64] &= !(1 << (skip % 64));
86+
87+
for n in self.neighbors(i).filter(|&n| n != skip) {
88+
connected = Self::intersect(connected, self.nodes[n]);
89+
connected[n / 64] |= 1 << (n % 64);
90+
91+
if connected.iter().map(|&n| n.count_ones()).sum::<u32>() != self.degree {
92+
continue 'sets;
93+
}
9494
}
95-
acc.push(self.nodes[i].name[0] as char);
96-
acc.push(self.nodes[i].name[1] as char);
97-
acc
98-
})
99-
}
10095

101-
fn try_add(&self, n: usize, assigned: &mut [bool], group: &mut Vec<usize>) {
102-
for &existing in group.iter() {
103-
if !self.nodes[n].edges.contains(&existing) {
104-
return;
96+
return Self::iter(connected).fold(String::new(), |mut acc, i| {
97+
if !acc.is_empty() {
98+
acc.push(',');
99+
}
100+
let name = [b'a' + (i / 26) as u8, b'a' + (i % 26) as u8];
101+
acc.push(name[0] as char);
102+
acc.push(name[1] as char);
103+
acc
104+
});
105105
}
106106
}
107107

108-
group.push(n);
109-
assigned[n] = true;
108+
panic!("no solution found")
109+
}
110110

111-
for &neighbour in self.nodes[n].edges.iter() {
112-
if !assigned[neighbour] {
113-
self.try_add(neighbour, assigned, group);
114-
}
111+
#[inline]
112+
fn neighbors(&self, n: usize) -> impl Iterator<Item = usize> {
113+
Self::iter(self.nodes[n])
114+
}
115+
116+
#[inline]
117+
fn iter(bitset: [u64; 11]) -> impl Iterator<Item = usize> {
118+
bitset.into_iter().enumerate().flat_map(|(element, b)| {
119+
BitIterator::ones(b).map(move |(bit, _)| element * 64 + bit as usize)
120+
})
121+
}
122+
123+
#[inline]
124+
fn intersect(mut bitset1: [u64; 11], bitset2: [u64; 11]) -> [u64; 11] {
125+
for (a, &b) in bitset1.iter_mut().zip(bitset2.iter()) {
126+
*a &= b;
115127
}
128+
bitset1
116129
}
117130
}
118131

119-
examples!(Day23 -> (u32, &'static str) []);
132+
examples!(Day23 -> (u32, &'static str) [
133+
{file: "day23_example0.txt", part1: 7, part2: "co,de,ka,ta"},
134+
]);

0 commit comments

Comments
 (0)