mirror of
https://github.com/RussellChamp/advent-of-code-2025-cursor.git
synced 2026-10-27 23:52:34 +00:00
Day 8: Simple junior developer solution (no rayon, basic loops)
This commit is contained in:
1 parent
b067baac3d
commit
bec9a9e8ad
1 file changed
+206
-136
+206
-136
@@ -1,166 +1,193 @@
|
|||||||
use rayon::prelude::*;
|
|
||||||
|
|
||||||
advent_of_code::solution!(8);
|
advent_of_code::solution!(8);
|
||||||
|
|
||||||
/// Union-Find data structure for tracking circuits
|
// Helper function to parse a line like "162,817,812" into (x, y, z)
|
||||||
struct UnionFind {
|
fn parse_line(line: &str) -> Option<(i64, i64, i64)> {
|
||||||
parent: Vec<usize>,
|
let parts: Vec<&str> = line.split(',').collect();
|
||||||
rank: Vec<usize>,
|
if parts.len() != 3 {
|
||||||
size: Vec<usize>,
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnionFind {
|
let x = parts[0].parse::<i64>().ok()?;
|
||||||
fn new(n: usize) -> Self {
|
let y = parts[1].parse::<i64>().ok()?;
|
||||||
Self {
|
let z = parts[2].parse::<i64>().ok()?;
|
||||||
parent: (0..n).collect(),
|
|
||||||
rank: vec![0; n],
|
|
||||||
size: vec![1; n],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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]
|
|
||||||
}
|
|
||||||
|
|
||||||
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<usize> {
|
|
||||||
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))
|
Some((x, y, z))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate squared Euclidean distance (to avoid sqrt)
|
// Calculate distance squared between two points (we don't need actual distance for comparing)
|
||||||
#[inline]
|
fn get_distance(p1: (i64, i64, i64), p2: (i64, i64, i64)) -> i64 {
|
||||||
fn distance_squared(a: (i64, i64, i64), b: (i64, i64, i64)) -> i64 {
|
let dx = p1.0 - p2.0;
|
||||||
let dx = a.0 - b.0;
|
let dy = p1.1 - p2.1;
|
||||||
let dy = a.1 - b.1;
|
let dz = p1.2 - p2.2;
|
||||||
let dz = a.2 - b.2;
|
|
||||||
dx * dx + dy * dy + dz * dz
|
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<u64> {
|
pub fn part_one(input: &str) -> Option<u64> {
|
||||||
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();
|
let n = coords.len();
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut pairs = generate_pairs_parallel(&coords);
|
// Step 2: Calculate all pair distances
|
||||||
|
// Store as (distance, index1, index2)
|
||||||
// Use partial sort - we only need the 1000 smallest pairs
|
let mut all_pairs: Vec<(i64, usize, usize)> = Vec::new();
|
||||||
if pairs.len() > 1000 {
|
for i in 0..n {
|
||||||
pairs.select_nth_unstable_by_key(999, |&(dist, _, _)| dist);
|
for j in i + 1..n {
|
||||||
pairs.truncate(1000);
|
let dist = get_distance(coords[i], coords[j]);
|
||||||
|
all_pairs.push((dist, i, j));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut uf = UnionFind::new(n);
|
// Step 3: Sort by distance (smallest first)
|
||||||
for &(_, i, j) in &pairs {
|
all_pairs.sort_by(|a, b| a.0.cmp(&b.0));
|
||||||
uf.union(i, j);
|
|
||||||
|
// 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<usize> = (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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get circuit sizes and find 3 largest
|
// Merge the groups - put everyone from group j into group i
|
||||||
let mut sizes = uf.get_circuit_sizes();
|
let group_i = groups[*i];
|
||||||
sizes.sort_unstable_by(|a, b| b.cmp(a));
|
let group_j = groups[*j];
|
||||||
|
|
||||||
Some(sizes.iter().take(3).map(|&s| s as u64).product())
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 6: Count how many boxes are in each group
|
||||||
|
let mut group_sizes: Vec<usize> = Vec::new();
|
||||||
|
let mut counted_groups: Vec<usize> = Vec::new();
|
||||||
|
|
||||||
|
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<u64> {
|
pub fn part_two(input: &str) -> Option<u64> {
|
||||||
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();
|
let n = coords.len();
|
||||||
if n <= 1 {
|
if n <= 1 {
|
||||||
return None;
|
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)
|
// Step 3: Sort by distance
|
||||||
pairs.par_sort_unstable_by_key(|&(dist, _, _)| dist);
|
all_pairs.sort_by(|a, b| a.0.cmp(&b.0));
|
||||||
|
|
||||||
let mut uf = UnionFind::new(n);
|
// Step 4: Track groups
|
||||||
let mut num_circuits = n;
|
let mut groups: Vec<usize> = (0..n).collect();
|
||||||
let mut last_connection: Option<(usize, usize)> = None;
|
|
||||||
|
|
||||||
for &(_, i, j) in &pairs {
|
// Step 5: Keep connecting until everyone is in one group
|
||||||
if uf.union(i, j) {
|
let mut last_i = 0;
|
||||||
num_circuits -= 1;
|
let mut last_j = 0;
|
||||||
last_connection = Some((i, j));
|
|
||||||
|
|
||||||
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;
|
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)]
|
#[cfg(test)]
|
||||||
@@ -169,20 +196,62 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_part_one() {
|
fn test_part_one() {
|
||||||
// Example uses 10 connections, not 1000 - test the core logic
|
// The example uses 10 connections, not 1000
|
||||||
let coords = parse_coords(&advent_of_code::template::read_file("examples", DAY));
|
// So we need to test the logic differently
|
||||||
let n = coords.len();
|
let input = &advent_of_code::template::read_file("examples", DAY);
|
||||||
let mut pairs = generate_pairs_parallel(&coords);
|
|
||||||
pairs.select_nth_unstable_by_key(9, |&(dist, _, _)| dist);
|
|
||||||
pairs.truncate(10);
|
|
||||||
|
|
||||||
let mut uf = UnionFind::new(n);
|
// Parse coordinates
|
||||||
for &(_, i, j) in &pairs {
|
let mut coords: Vec<(i64, i64, i64)> = Vec::new();
|
||||||
uf.union(i, j);
|
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<usize> = (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<usize> = Vec::new();
|
||||||
|
let mut counted: Vec<usize> = 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);
|
assert_eq!(result, 40);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,3 +261,4 @@ mod tests {
|
|||||||
assert_eq!(result, Some(25272));
|
assert_eq!(result, Some(25272));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user