Skip to content

Commit c58f563

Browse files
committed
Day 12
1 parent 379274e commit c58f563

File tree

1 file changed

+379
-0
lines changed

1 file changed

+379
-0
lines changed

src/day12.rs

Lines changed: 379 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
1+
use std::{
2+
cmp::minmax_by_key,
3+
collections::{HashMap, HashSet},
4+
ops::Index,
5+
};
6+
7+
pub fn day12(input: &str) -> (usize, usize) {
8+
let garden = Garden::new(input);
9+
10+
(part1(&garden), part2(&garden))
11+
}
12+
13+
fn part1(garden: &Garden) -> usize {
14+
garden.iter_regions().map(|r| cost1(&r, &garden)).sum()
15+
}
16+
17+
fn part2(garden: &Garden) -> usize {
18+
garden.iter_regions().map(|r| cost2(&r, &garden)).sum()
19+
}
20+
21+
fn cost1(region: &HashSet<MonadicIndex>, garden: &Garden) -> usize {
22+
region
23+
.iter()
24+
.map(|&i| fences(i, garden).count())
25+
.sum::<usize>()
26+
* region.len()
27+
}
28+
29+
fn cost2(region: &HashSet<MonadicIndex>, garden: &Garden) -> usize {
30+
// give each fence an index, keep lookup fast
31+
let all_fences: HashMap<Fence, usize> = region
32+
.iter()
33+
.flat_map(|&i| fences(i, garden))
34+
.enumerate()
35+
.map(|(i, f)| (f, i))
36+
.collect();
37+
38+
let mut uf = UnionFind::new(all_fences.len());
39+
for (fence, &index) in all_fences.iter() {
40+
if let Some(&next) = fence.next(garden).and_then(|f| all_fences.get(&f)) {
41+
uf.union(index, next);
42+
}
43+
}
44+
45+
let sides = uf.into_sets().count();
46+
47+
sides * region.len()
48+
}
49+
50+
fn fences(index: MonadicIndex, garden: &Garden) -> impl Iterator<Item = Fence> + use<'_> {
51+
let here = garden.to_diadic(index);
52+
[
53+
Direction::Left,
54+
Direction::Right,
55+
Direction::Up,
56+
Direction::Down,
57+
]
58+
.into_iter()
59+
.filter_map(move |side| {
60+
let needs_fence = garden
61+
.neighbor(here, side)
62+
.map_or(true, |neighbor| garden[here] != garden[neighbor]);
63+
needs_fence.then_some(Fence {
64+
location: here,
65+
side,
66+
})
67+
})
68+
}
69+
70+
#[derive(Debug)]
71+
struct Garden {
72+
rows: usize,
73+
cols: usize,
74+
plots: Vec<u8>,
75+
}
76+
77+
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
78+
struct MonadicIndex(usize);
79+
80+
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
81+
struct DiadicIndex {
82+
row: usize,
83+
col: usize,
84+
}
85+
86+
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
87+
struct Fence {
88+
location: DiadicIndex,
89+
side: Direction,
90+
}
91+
92+
93+
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
94+
enum Direction {
95+
Left,
96+
Right,
97+
Up,
98+
Down,
99+
}
100+
101+
impl Direction {
102+
fn counter_clockwise(self) -> Direction {
103+
match self {
104+
Self::Up => Self::Left,
105+
Self::Left => Self::Down,
106+
Self::Down => Self::Right,
107+
Self::Right => Self::Up,
108+
}
109+
}
110+
}
111+
112+
impl Fence {
113+
// next fence, the direction is defined such that
114+
// the inside is to the left
115+
fn next(&self, garden: &Garden) -> Option<Fence> {
116+
garden
117+
.neighbor(self.location, self.side.counter_clockwise())
118+
.map(|l| Fence {
119+
location: l,
120+
side: self.side,
121+
})
122+
}
123+
}
124+
125+
impl Garden {
126+
fn new(input: &str) -> Self {
127+
let (rows, cols) = (
128+
input.chars().take_while(|&c| c != '\n').count(),
129+
input.lines().count(),
130+
);
131+
let plots = input
132+
.lines()
133+
.flat_map(|l| l.as_bytes().to_owned())
134+
.collect();
135+
Garden { rows, cols, plots }
136+
}
137+
138+
fn iter_regions(&self) -> impl Iterator<Item = HashSet<MonadicIndex>> {
139+
let mut uf = UnionFind::new(self.plots.len());
140+
let mut union = |x, y| uf.union(self.to_monadic(x).0, self.to_monadic(y).0);
141+
142+
for i in 0..self.rows * self.cols {
143+
let here = self.to_diadic(MonadicIndex(i));
144+
if let Some(down) = self
145+
.neighbor(here, Direction::Down)
146+
.filter(|&down| self[down] == self[here])
147+
{
148+
union(here, down);
149+
}
150+
151+
if let Some(right) = self
152+
.neighbor(here, Direction::Right)
153+
.filter(|&right| self[right] == self[here])
154+
{
155+
union(here, right);
156+
}
157+
}
158+
159+
uf.into_sets()
160+
.map(|s| s.into_iter().map(|i| MonadicIndex(i)).collect())
161+
}
162+
163+
fn to_monadic(&self, index: DiadicIndex) -> MonadicIndex {
164+
if index.row >= self.rows || index.col >= self.cols {
165+
panic!("index out of range");
166+
}
167+
168+
MonadicIndex(index.row * self.cols + index.col)
169+
}
170+
171+
fn to_diadic(&self, index: MonadicIndex) -> DiadicIndex {
172+
DiadicIndex {
173+
row: index.0 / self.cols,
174+
col: index.0 % self.cols,
175+
}
176+
}
177+
178+
fn neighbor(&self, location: DiadicIndex, direction: Direction) -> Option<DiadicIndex> {
179+
match direction {
180+
Direction::Up => (location.row > 0).then(|| DiadicIndex {
181+
row: location.row - 1,
182+
..location
183+
}),
184+
Direction::Down => (location.row < self.rows - 1).then(|| DiadicIndex {
185+
row: location.row + 1,
186+
..location
187+
}),
188+
Direction::Left => (location.col > 0).then(|| DiadicIndex {
189+
col: location.col - 1,
190+
..location
191+
}),
192+
Direction::Right => (location.col < self.cols - 1).then(|| DiadicIndex {
193+
col: location.col + 1,
194+
..location
195+
}),
196+
}
197+
}
198+
}
199+
200+
impl Index<MonadicIndex> for Garden {
201+
type Output = u8;
202+
fn index(&self, index: MonadicIndex) -> &Self::Output {
203+
&self.plots[index.0]
204+
}
205+
}
206+
207+
impl Index<DiadicIndex> for Garden {
208+
type Output = u8;
209+
fn index(&self, index: DiadicIndex) -> &Self::Output {
210+
&self[self.to_monadic(index)]
211+
}
212+
}
213+
214+
// partitions a set into disjoint subsets
215+
// every subset is represented by a node
216+
struct UnionFind {
217+
// stores sets as trees, every element
218+
// contains index of parent node.
219+
// roots contain their own index
220+
nodes: Vec<Node>,
221+
}
222+
223+
#[derive(Clone, Copy, PartialEq, Debug)]
224+
struct Node {
225+
// index of the parent, if this points
226+
// to itself its a root
227+
parent: usize,
228+
// number of descendents,
229+
// only valid for root nodes
230+
size: usize,
231+
}
232+
233+
// being generic in the index sucks
234+
// impl<I: Into<usize> + From<usize> + Copy + PartialEq + PartialOrd + AddAssign> UnionFind<I> {
235+
impl UnionFind {
236+
fn new(size: usize) -> Self {
237+
UnionFind {
238+
nodes: (0..)
239+
.take(size)
240+
.map(|i| Node { parent: i, size: 1 })
241+
.collect(),
242+
}
243+
}
244+
245+
fn find(&mut self, x: usize) -> usize {
246+
let node = self.nodes[x];
247+
if node.parent != x {
248+
self.nodes[x].parent = self.find(node.parent);
249+
return self.nodes[x].parent;
250+
}
251+
x
252+
}
253+
254+
fn union(&mut self, x: usize, y: usize) {
255+
let root_x_idx = self.find(x);
256+
let root_y_idx = self.find(y);
257+
258+
if root_x_idx == root_y_idx {
259+
return;
260+
}
261+
262+
// to prevent trees from becoming too deep, make sure to add the smaller tree to the larger
263+
let [smaller_idx, larger_idx] =
264+
minmax_by_key(root_x_idx, root_y_idx, |&idx| self.nodes[idx].size);
265+
266+
self.nodes[smaller_idx].parent = larger_idx;
267+
self.nodes[larger_idx].size += self.nodes[smaller_idx].size;
268+
}
269+
270+
fn into_sets(mut self) -> impl Iterator<Item = HashSet<usize>> {
271+
let mut groups: HashMap<usize, HashSet<usize>> = HashMap::new();
272+
273+
for i in 0..self.nodes.len() {
274+
let root = self.find(i);
275+
groups.entry(root).or_insert_with(HashSet::new).insert(i);
276+
}
277+
278+
groups.into_values()
279+
}
280+
}
281+
282+
// thanks Claude
283+
#[cfg(test)]
284+
mod tests {
285+
use super::*;
286+
287+
#[test]
288+
fn test_find_root() {
289+
let mut uf = UnionFind::new(3);
290+
assert_eq!(uf.find(0), 0);
291+
assert_eq!(uf.find(1), 1);
292+
assert_eq!(uf.find(2), 2);
293+
}
294+
295+
#[test]
296+
fn test_union_basic() {
297+
let mut uf = UnionFind::new(4);
298+
uf.union(0, 1);
299+
assert_eq!(uf.find(0), uf.find(1));
300+
assert_ne!(uf.find(0), uf.find(2));
301+
}
302+
303+
#[test]
304+
fn test_union_multiple() {
305+
let mut uf = UnionFind::new(5);
306+
uf.union(0, 1);
307+
uf.union(1, 2);
308+
uf.union(3, 4);
309+
310+
assert_eq!(uf.find(0), uf.find(1));
311+
assert_eq!(uf.find(1), uf.find(2));
312+
assert_eq!(uf.find(3), uf.find(4));
313+
assert_ne!(uf.find(0), uf.find(3));
314+
}
315+
316+
#[test]
317+
fn test_union_same_element() {
318+
let mut uf = UnionFind::new(3);
319+
uf.union(1, 1);
320+
assert_eq!(uf.find(1), 1);
321+
}
322+
323+
#[test]
324+
fn test_path_compression() {
325+
let mut uf = UnionFind::new(4);
326+
uf.union(0, 1);
327+
uf.union(1, 2);
328+
uf.union(2, 3);
329+
330+
// First find should trigger path compression
331+
let root = uf.find(3);
332+
// All nodes should now point directly to root
333+
assert_eq!(uf.nodes[0].parent, root);
334+
assert_eq!(uf.nodes[1].parent, root);
335+
assert_eq!(uf.nodes[2].parent, root);
336+
assert_eq!(uf.nodes[3].parent, root);
337+
}
338+
339+
#[test]
340+
fn test_disjoint_sets() {
341+
let mut uf = UnionFind::new(6);
342+
uf.union(0, 1);
343+
uf.union(2, 3);
344+
uf.union(4, 5);
345+
346+
// Three separate components
347+
assert_ne!(uf.find(0), uf.find(2));
348+
assert_ne!(uf.find(0), uf.find(4));
349+
assert_ne!(uf.find(2), uf.find(4));
350+
}
351+
352+
#[test]
353+
fn test_union_find_into_sets() {
354+
let mut uf = UnionFind::new(6);
355+
356+
// Create some unions: {0,1,2}, {3,4}, {5}
357+
uf.union(0, 1);
358+
uf.union(1, 2);
359+
uf.union(3, 4);
360+
361+
let sets: Vec<HashSet<usize>> = uf.into_sets().collect();
362+
363+
// Should have 3 sets
364+
assert_eq!(sets.len(), 3);
365+
366+
// Check that we have the expected sets (order doesn't matter)
367+
let mut found_sets = Vec::new();
368+
for set in sets {
369+
found_sets.push(set);
370+
}
371+
372+
// Sort by size for consistent testing
373+
found_sets.sort_by_key(|s| s.len());
374+
375+
assert_eq!(found_sets[0], HashSet::from([5])); // singleton
376+
assert_eq!(found_sets[1], HashSet::from([3, 4])); // pair
377+
assert_eq!(found_sets[2], HashSet::from([0, 1, 2])); // triple
378+
}
379+
}

0 commit comments

Comments
 (0)