Skip to content

Commit d8898b4

Browse files
committed
2024 day 23
This solution currently assumes each node is in a single clique, which works for my input but not the example. Part 2 output is 38 characters, which required redistributing output column widths.
1 parent 39649b2 commit d8898b4

File tree

3 files changed

+130
-7
lines changed

3 files changed

+130
-7
lines changed

crates/aoc/src/main.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,12 @@ fn main() {
3636
}
3737

3838
// FIXME support 80 character wide output (without time?)
39-
println!("Puzzle │ Part 1 │ Part 2 │ Time ");
40-
println!("────────┼──────────────────────────────────┼──────────────────────────────────┼───────────");
41-
39+
println!(
40+
"Puzzle │ Part 1 │ Part 2 │ Time "
41+
);
42+
println!(
43+
"────────┼──────────────────────┼────────────────────────────────────────┼───────────"
44+
);
4245
let mut total = Duration::default();
4346
for (year, day, f) in puzzles {
4447
let input = match read_input(year, day) {
@@ -57,8 +60,8 @@ fn main() {
5760

5861
// Hack to treat "🎄" as two characters wide
5962
// ("🎄" is 1 wide in Unicode 8 but 2 wide in Unicode 9+)
60-
let part1_width = if part1 == "🎄" { 31 } else { 32 };
61-
let part2_width = if part2 == "🎄" { 31 } else { 32 };
63+
let part1_width = if part1 == "🎄" { 19 } else { 20 };
64+
let part2_width = if part2 == "🎄" { 37 } else { 38 };
6265

6366
println!(
6467
"{year:#} {day:#} │ {part1:<part1_width$} │ {part2:<part2_width$} │ {}",
@@ -73,10 +76,10 @@ fn main() {
7376
}
7477

7578
println!(
76-
"──────────────────────────────────────────┴──────────────────────────────────┼───────────"
79+
"──────────────────────────────────────────────────────────────────────┼───────────"
7780
);
7881
println!(
79-
" │ {}",
82+
" │ {}",
8083
format_duration(total),
8184
);
8285
}

crates/year2024/src/day23.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use utils::prelude::*;
2+
3+
/// Finding the largest clique in a graph.
4+
#[derive(Clone, Debug)]
5+
pub struct Day23 {
6+
nodes: Vec<Node>,
7+
}
8+
9+
#[derive(Clone, Debug)]
10+
struct Node {
11+
name: [u8; 2],
12+
edges: Vec<usize>,
13+
}
14+
15+
impl Day23 {
16+
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
17+
let mut indexes = [None; 26 * 26];
18+
let mut nodes = vec![];
19+
20+
for item in parser::byte_range(b'a'..=b'z')
21+
.repeat_n(parser::noop())
22+
.repeat_n(b'-')
23+
.with_suffix(parser::eol())
24+
.parse_iterator(input)
25+
{
26+
let [n1, n2] = item?;
27+
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+
};
43+
44+
let index1 = index_of(n1);
45+
let index2 = index_of(n2);
46+
47+
nodes[index1].edges.push(index2);
48+
nodes[index2].edges.push(index1);
49+
}
50+
51+
Ok(Self { nodes })
52+
}
53+
54+
#[must_use]
55+
pub fn part1(&self) -> u32 {
56+
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;
61+
}
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+
{
67+
count += 1;
68+
}
69+
}
70+
}
71+
}
72+
count
73+
}
74+
75+
#[must_use]
76+
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());
79+
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+
}
86+
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(',');
94+
}
95+
acc.push(self.nodes[i].name[0] as char);
96+
acc.push(self.nodes[i].name[1] as char);
97+
acc
98+
})
99+
}
100+
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;
105+
}
106+
}
107+
108+
group.push(n);
109+
assigned[n] = true;
110+
111+
for &neighbour in self.nodes[n].edges.iter() {
112+
if !assigned[neighbour] {
113+
self.try_add(neighbour, assigned, group);
114+
}
115+
}
116+
}
117+
}
118+
119+
examples!(Day23 -> (u32, &'static str) []);

crates/year2024/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ utils::year!(2024 => year2024, ${
2424
20 => day20::Day20,
2525
21 => day21::Day21,
2626
22 => day22::Day22,
27+
23 => day23::Day23,
2728
});

0 commit comments

Comments
 (0)