diff --git a/src/bin/08.rs b/src/bin/08.rs index ca1a49c..8270aae 100644 --- a/src/bin/08.rs +++ b/src/bin/08.rs @@ -1,166 +1,193 @@ -use rayon::prelude::*; - advent_of_code::solution!(8); -/// Union-Find data structure for tracking circuits -struct UnionFind { - parent: Vec, - rank: Vec, - size: Vec, -} - -impl UnionFind { - fn new(n: usize) -> Self { - Self { - parent: (0..n).collect(), - rank: vec![0; n], - size: vec![1; n], - } +// Helper function to parse a line like "162,817,812" into (x, y, z) +fn parse_line(line: &str) -> Option<(i64, i64, i64)> { + let parts: Vec<&str> = line.split(',').collect(); + if parts.len() != 3 { + return None; } - fn find(&mut self, x: usize) -> usize { - if self.parent[x] != x { - self.parent[x] = self.find(self.parent[x]); // Path compression - } - self.parent[x] - } + let x = parts[0].parse::().ok()?; + let y = parts[1].parse::().ok()?; + let z = parts[2].parse::().ok()?; - fn union(&mut self, x: usize, y: usize) -> bool { - let root_x = self.find(x); - let root_y = self.find(y); - - if root_x == root_y { - return false; // Already in same circuit - } - - // Union by rank - match self.rank[root_x].cmp(&self.rank[root_y]) { - std::cmp::Ordering::Less => { - self.parent[root_x] = root_y; - self.size[root_y] += self.size[root_x]; - } - std::cmp::Ordering::Greater => { - self.parent[root_y] = root_x; - self.size[root_x] += self.size[root_y]; - } - std::cmp::Ordering::Equal => { - self.parent[root_y] = root_x; - self.size[root_x] += self.size[root_y]; - self.rank[root_x] += 1; - } - } - true - } - - fn get_circuit_sizes(&mut self) -> Vec { - let n = self.parent.len(); - let mut sizes = Vec::new(); - for i in 0..n { - if self.find(i) == i { - sizes.push(self.size[i]); - } - } - sizes - } -} - -/// Parse a line "X,Y,Z" into coordinates -fn parse_coord(line: &str) -> Option<(i64, i64, i64)> { - let mut parts = line.split(','); - let x = parts.next()?.parse().ok()?; - let y = parts.next()?.parse().ok()?; - let z = parts.next()?.parse().ok()?; Some((x, y, z)) } -/// Calculate squared Euclidean distance (to avoid sqrt) -#[inline] -fn distance_squared(a: (i64, i64, i64), b: (i64, i64, i64)) -> i64 { - let dx = a.0 - b.0; - let dy = a.1 - b.1; - let dz = a.2 - b.2; +// Calculate distance squared between two points (we don't need actual distance for comparing) +fn get_distance(p1: (i64, i64, i64), p2: (i64, i64, i64)) -> i64 { + let dx = p1.0 - p2.0; + let dy = p1.1 - p2.1; + let dz = p1.2 - p2.2; dx * dx + dy * dy + dz * dz } -/// Parse coordinates from input -fn parse_coords(input: &str) -> Vec<(i64, i64, i64)> { - input - .lines() - .filter(|line| !line.trim().is_empty()) - .filter_map(parse_coord) - .collect() -} - -/// Generate all pairs with distances in parallel -fn generate_pairs_parallel(coords: &[(i64, i64, i64)]) -> Vec<(i64, usize, usize)> { - let n = coords.len(); - (0..n) - .into_par_iter() - .flat_map_iter(|i| { - let coords = coords; - ((i + 1)..n).map(move |j| { - let dist = distance_squared(coords[i], coords[j]); - (dist, i, j) - }) - }) - .collect() -} - pub fn part_one(input: &str) -> Option { - let coords = parse_coords(input); + // Step 1: Parse all the coordinates + let mut coords: Vec<(i64, i64, i64)> = Vec::new(); + for line in input.lines() { + let line = line.trim(); + if line.is_empty() { + continue; + } + if let Some(coord) = parse_line(line) { + coords.push(coord); + } + } + let n = coords.len(); if n == 0 { return None; } - let mut pairs = generate_pairs_parallel(&coords); - - // Use partial sort - we only need the 1000 smallest pairs - if pairs.len() > 1000 { - pairs.select_nth_unstable_by_key(999, |&(dist, _, _)| dist); - pairs.truncate(1000); + // Step 2: Calculate all pair distances + // Store as (distance, index1, index2) + let mut all_pairs: Vec<(i64, usize, usize)> = Vec::new(); + for i in 0..n { + for j in i + 1..n { + let dist = get_distance(coords[i], coords[j]); + all_pairs.push((dist, i, j)); + } } - let mut uf = UnionFind::new(n); - for &(_, i, j) in &pairs { - uf.union(i, j); + // Step 3: Sort by distance (smallest first) + all_pairs.sort_by(|a, b| a.0.cmp(&b.0)); + + // Step 4: Track which group each junction box belongs to + // Initially each box is in its own group (group number = its index) + let mut groups: Vec = (0..n).collect(); + + // Step 5: Connect the first 1000 pairs + let connections_to_make = 1000; + let mut connections_made = 0; + + for (_, i, j) in &all_pairs { + if connections_made >= connections_to_make { + break; + } + + // Merge the groups - put everyone from group j into group i + let group_i = groups[*i]; + let group_j = groups[*j]; + + if group_i != group_j { + // Merge: change all boxes in group_j to be in group_i + for k in 0..n { + if groups[k] == group_j { + groups[k] = group_i; + } + } + } + + connections_made += 1; } - // Get circuit sizes and find 3 largest - let mut sizes = uf.get_circuit_sizes(); - sizes.sort_unstable_by(|a, b| b.cmp(a)); + // Step 6: Count how many boxes are in each group + let mut group_sizes: Vec = Vec::new(); + let mut counted_groups: Vec = Vec::new(); - Some(sizes.iter().take(3).map(|&s| s as u64).product()) + for i in 0..n { + let my_group = groups[i]; + if !counted_groups.contains(&my_group) { + // Count how many are in this group + let mut count = 0; + for j in 0..n { + if groups[j] == my_group { + count += 1; + } + } + group_sizes.push(count); + counted_groups.push(my_group); + } + } + + // Step 7: Sort sizes descending and multiply top 3 + group_sizes.sort_by(|a, b| b.cmp(a)); + + let mut result: u64 = 1; + for i in 0..3 { + if i < group_sizes.len() { + result *= group_sizes[i] as u64; + } + } + + Some(result) } pub fn part_two(input: &str) -> Option { - let coords = parse_coords(input); + // Step 1: Parse all the coordinates + let mut coords: Vec<(i64, i64, i64)> = Vec::new(); + for line in input.lines() { + let line = line.trim(); + if line.is_empty() { + continue; + } + if let Some(coord) = parse_line(line) { + coords.push(coord); + } + } + let n = coords.len(); if n <= 1 { return None; } - let mut pairs = generate_pairs_parallel(&coords); + // Step 2: Calculate all pair distances + let mut all_pairs: Vec<(i64, usize, usize)> = Vec::new(); + for i in 0..n { + for j in i + 1..n { + let dist = get_distance(coords[i], coords[j]); + all_pairs.push((dist, i, j)); + } + } - // Full parallel sort needed for part 2 (process in order) - pairs.par_sort_unstable_by_key(|&(dist, _, _)| dist); + // Step 3: Sort by distance + all_pairs.sort_by(|a, b| a.0.cmp(&b.0)); - let mut uf = UnionFind::new(n); - let mut num_circuits = n; - let mut last_connection: Option<(usize, usize)> = None; + // Step 4: Track groups + let mut groups: Vec = (0..n).collect(); - for &(_, i, j) in &pairs { - if uf.union(i, j) { - num_circuits -= 1; - last_connection = Some((i, j)); + // Step 5: Keep connecting until everyone is in one group + let mut last_i = 0; + let mut last_j = 0; - if num_circuits == 1 { + for (_, i, j) in &all_pairs { + let group_i = groups[*i]; + let group_j = groups[*j]; + + if group_i != group_j { + // This connection actually merges two different groups + last_i = *i; + last_j = *j; + + // Merge the groups + for k in 0..n { + if groups[k] == group_j { + groups[k] = group_i; + } + } + + // Check if everyone is now in the same group + let first_group = groups[0]; + let mut all_same = true; + for k in 1..n { + if groups[k] != first_group { + all_same = false; + break; + } + } + + if all_same { break; } } } - last_connection.map(|(i, j)| coords[i].0 as u64 * coords[j].0 as u64) + // Step 6: Return X coordinates multiplied + let x1 = coords[last_i].0 as u64; + let x2 = coords[last_j].0 as u64; + + Some(x1 * x2) } #[cfg(test)] @@ -169,20 +196,62 @@ mod tests { #[test] fn test_part_one() { - // Example uses 10 connections, not 1000 - test the core logic - let coords = parse_coords(&advent_of_code::template::read_file("examples", DAY)); - let n = coords.len(); - let mut pairs = generate_pairs_parallel(&coords); - pairs.select_nth_unstable_by_key(9, |&(dist, _, _)| dist); - pairs.truncate(10); + // The example uses 10 connections, not 1000 + // So we need to test the logic differently + let input = &advent_of_code::template::read_file("examples", DAY); - let mut uf = UnionFind::new(n); - for &(_, i, j) in &pairs { - uf.union(i, j); + // Parse coordinates + let mut coords: Vec<(i64, i64, i64)> = Vec::new(); + for line in input.lines() { + let line = line.trim(); + if !line.is_empty() { + if let Some(coord) = parse_line(line) { + coords.push(coord); + } + } } - let mut sizes = uf.get_circuit_sizes(); - sizes.sort_unstable_by(|a, b| b.cmp(a)); - let result: u64 = sizes.iter().take(3).map(|&s| s as u64).product(); + + let n = coords.len(); + + // Get all pairs sorted + let mut all_pairs: Vec<(i64, usize, usize)> = Vec::new(); + for i in 0..n { + for j in i + 1..n { + let dist = get_distance(coords[i], coords[j]); + all_pairs.push((dist, i, j)); + } + } + all_pairs.sort_by(|a, b| a.0.cmp(&b.0)); + + // Connect 10 pairs + let mut groups: Vec = (0..n).collect(); + for k in 0..10 { + let (_, i, j) = all_pairs[k]; + let group_i = groups[i]; + let group_j = groups[j]; + if group_i != group_j { + for m in 0..n { + if groups[m] == group_j { + groups[m] = group_i; + } + } + } + } + + // Count group sizes + let mut group_sizes: Vec = Vec::new(); + let mut counted: Vec = Vec::new(); + for i in 0..n { + if !counted.contains(&groups[i]) { + let count = groups.iter().filter(|&&g| g == groups[i]).count(); + group_sizes.push(count); + counted.push(groups[i]); + } + } + + group_sizes.sort_by(|a, b| b.cmp(a)); + let result: u64 = group_sizes.iter().take(3).map(|&s| s as u64).product(); + assert_eq!(result, 40); } @@ -192,3 +261,4 @@ mod tests { assert_eq!(result, Some(25272)); } } +