use std::cmp::Reverse; use std::error::Error; use super::Solution; pub struct Day08 {} impl Solution for Day08 { fn part1( &self, input: &mut Vec, ) -> Result, Box> { let points = self.parse_points(input)?; if points.is_empty() { eprintln!("No points provided."); return Err("no points".into()); } // default k = 1000 for the puzzle, but allow override with env var or small change here let k = 1000usize; // for test input Ok(Box::new(self.solve_part1(&points, k))) } fn part2( &self, input: &mut Vec, ) -> Result, Box> { let points = self.parse_points(input)?; if points.is_empty() { eprintln!("No points provided."); return Err("no points".into()); } Ok(Box::new(self.solve_part2(&points))) } fn get_day(&self) -> u8 { 8 } } impl Day08 { /// Build all pairwise edges from points (upper triangle). fn build_edges(&self, points: &[Point]) -> Vec { let n = points.len(); let mut edges = Vec::with_capacity(n.saturating_mul(n.saturating_sub(1)) / 2); for i in 0..n { for j in (i + 1)..n { edges.push(Edge { a: i, b: j, dist2: points[i].dist2(&points[j]), }); } } edges } /// Solve: take first k edges sorted by distance, union them, then multiply top 3 component sizes. fn solve_part1(&self, points: &[Point], k: usize) -> u128 { let n = points.len(); if n == 0 { return 0; } let mut edges = self.build_edges(points); // sort ascending by distance (stable) edges.sort_by_key(|e| e.dist2); let mut uf = UnionFind::new(n); let take = k.min(edges.len()); for e in edges.into_iter().take(take) { uf.union(e.a, e.b); } let mut sizes = uf.sizes(); sizes.sort_by_key(|&s| Reverse(s)); // multiply top 3 (or fewer if less) let mut prod: u128 = 1; for s in sizes.iter().take(3) { prod = prod.saturating_mul(*s as u128); } prod } /// Part Two: /// walk through edges; when union_count == n-1, return X_a * X_b of that last union. fn solve_part2(&self, points: &[Point]) -> i128 { let n = points.len(); if n <= 1 { return 0; } let mut edges = self.build_edges(points); edges.sort_by_key(|e| e.dist2); let mut uf = UnionFind::new(n); let mut unions = 0usize; let target = n - 1; for e in edges { if uf.union(e.a, e.b) { unions += 1; if unions == target { let xa = points[e.a].x as i128; let xb = points[e.b].x as i128; return xa * xb; } } } // should never happen unless input is malformed 0 } /// Parse many lines of "x,y,z". fn parse_points(&self, input: &[String]) -> Result, Box> { let mut points = Vec::new(); for (idx, line) in input.iter().enumerate() { let line = line.trim(); if line.is_empty() { continue; } match Point::from_csv_line(line) { Ok(p) => points.push(p), Err(e) => return Err(format!("line {}: {}", idx + 1, e).into()), } } Ok(points) } } /// A 3D point with integer coords. #[derive(Debug, Clone, Copy)] struct Point { x: i64, y: i64, z: i64, } impl Point { fn from_csv_line(s: &str) -> Result> { let parts: Vec<_> = s.trim().split(',').collect(); if parts.len() != 3 { return Err(format!("invalid point line: {}", s).into()); } let x = parts[0].parse::()?; let y = parts[1].parse::()?; let z = parts[2].parse::()?; Ok(Point { x, y, z }) } /// squared Euclidean distance, as u128 to be safe. fn dist2(&self, other: &Point) -> u128 { let dx = (self.x - other.x) as i128; let dy = (self.y - other.y) as i128; let dz = (self.z - other.z) as i128; let s = dx * dx + dy * dy + dz * dz; if s < 0 { // should never happen, but cast safely (-s) as u128 } else { s as u128 } } } /// Edge between two indices with squared distance. #[derive(Debug, Clone)] struct Edge { a: usize, b: usize, dist2: u128, } /// Simple union-find / disjoint-set with union by size + path compression. #[derive(Debug)] struct UnionFind { parent: Vec, // if negative, -size; else parent index } impl UnionFind { fn new(n: usize) -> Self { UnionFind { parent: vec![-1; n], } } fn find(&mut self, x: usize) -> usize { // path compression let mut root = x; while self.parent[root] >= 0 { root = self.parent[root] as usize; } // compress let mut cur = x; while cur != root { let next = self.parent[cur] as usize; self.parent[cur] = root as isize; cur = next; } root } fn union(&mut self, a: usize, b: usize) -> bool { let mut ra = self.find(a); let mut rb = self.find(b); if ra == rb { return false; } let sa = (-self.parent[ra]) as usize; let sb = (-self.parent[rb]) as usize; // union by size: attach smaller to larger if sa < sb { std::mem::swap(&mut ra, &mut rb); } // now ra has >= size self.parent[ra] = -((sa + sb) as isize); self.parent[rb] = ra as isize; true } fn sizes(&self) -> Vec { let n = self.parent.len(); let mut out = Vec::new(); for i in 0..n { if self.parent[i] < 0 { out.push((-self.parent[i]) as usize); } } out } } /// Test from puzzle input #[cfg(test)] mod test { use super::*; use crate::*; #[test] fn part1() { let challenge = day08::Day08 {}; //Complete the Challenge let answer = challenge .part1( utils::get_input(challenge.get_day(), utils::InputType::Test1) .unwrap() .as_mut(), ) .unwrap() .to_string(); assert_eq!(answer, "20"); } #[test] fn part2() { let challenge = day08::Day08 {}; //Complete the Challenge let answer = challenge .part2( utils::get_input(challenge.get_day(), utils::InputType::Test2) .unwrap() .as_mut(), ) .unwrap() .to_string(); assert_eq!(answer, "25272"); } }