|
| 1 | +use std::{cell::RefCell, collections::HashSet, str::FromStr}; |
| 2 | + |
| 3 | +use anyhow::{Context, Result, ensure}; |
| 4 | +use itertools::Itertools; |
| 5 | + |
| 6 | +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] |
| 7 | +struct Coord { |
| 8 | + x: i64, |
| 9 | + y: i64, |
| 10 | + z: i64, |
| 11 | +} |
| 12 | + |
| 13 | +impl Coord { |
| 14 | + fn dist_squared(&self, other: &Coord) -> i64 { |
| 15 | + let xd = self.x - other.x; |
| 16 | + let yd = self.y - other.y; |
| 17 | + let zd = self.z - other.z; |
| 18 | + xd * xd + yd * yd + zd * zd |
| 19 | + } |
| 20 | +} |
| 21 | + |
| 22 | +impl FromStr for Coord { |
| 23 | + type Err = anyhow::Error; |
| 24 | + |
| 25 | + fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { |
| 26 | + let (x, y, z) = s |
| 27 | + .split(",") |
| 28 | + .collect_tuple() |
| 29 | + .context("incorrect number of elements in line")?; |
| 30 | + |
| 31 | + Ok(Coord { |
| 32 | + x: x.parse()?, |
| 33 | + y: y.parse()?, |
| 34 | + z: z.parse()?, |
| 35 | + }) |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +fn calculate_p1(circuits: &[RefCell<HashSet<&Coord>>]) -> usize { |
| 40 | + circuits |
| 41 | + .iter() |
| 42 | + .map(|c| c.borrow().len()) |
| 43 | + .sorted_unstable() |
| 44 | + .rev() |
| 45 | + .take(3) |
| 46 | + .product() |
| 47 | +} |
| 48 | + |
| 49 | +fn calculate<const P1_CONNECTIONS: usize>(inp: &str) -> Result<(usize, i64)> { |
| 50 | + let coords = inp |
| 51 | + .lines() |
| 52 | + .map(|line| line.parse::<Coord>()) |
| 53 | + .collect::<Result<Vec<_>>>()?; |
| 54 | + |
| 55 | + let mut distances = coords |
| 56 | + .iter() |
| 57 | + .tuple_combinations() |
| 58 | + .map(|(b1, b2)| (b1.dist_squared(b2), b1, b2)) |
| 59 | + .collect::<Vec<_>>(); |
| 60 | + |
| 61 | + distances.sort_unstable_by_key(|&(dist, _, _)| dist); |
| 62 | + |
| 63 | + let mut circuits: Vec<RefCell<HashSet<&Coord>>> = vec![]; |
| 64 | + let mut connections = 0; |
| 65 | + |
| 66 | + let mut p1 = 0; |
| 67 | + let mut p2 = 0; |
| 68 | + |
| 69 | + for (_, b1, b2) in distances { |
| 70 | + let mut added = false; |
| 71 | + for c in circuits.iter() { |
| 72 | + let mut c = c.borrow_mut(); |
| 73 | + if c.contains(b1) || c.contains(b2) { |
| 74 | + c.insert(b1); |
| 75 | + c.insert(b2); |
| 76 | + for c2 in circuits.iter() { |
| 77 | + if let Ok(mut c2) = c2.try_borrow_mut() |
| 78 | + && (c2.contains(b1) || c2.contains(b2)) |
| 79 | + { |
| 80 | + c.extend(c2.iter()); |
| 81 | + c2.clear(); |
| 82 | + } |
| 83 | + } |
| 84 | + added = true; |
| 85 | + break; |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + if !added { |
| 90 | + let new_circuit = HashSet::from([b1, b2]); |
| 91 | + circuits.push(RefCell::new(new_circuit)); |
| 92 | + } |
| 93 | + |
| 94 | + circuits.retain(|c| !c.borrow().is_empty()); |
| 95 | + |
| 96 | + connections += 1; |
| 97 | + |
| 98 | + if connections == P1_CONNECTIONS { |
| 99 | + p1 = calculate_p1(&circuits); |
| 100 | + } |
| 101 | + |
| 102 | + let max_circuit_length = circuits |
| 103 | + .iter() |
| 104 | + .map(|c| c.borrow().len()) |
| 105 | + .max() |
| 106 | + .context("no circuits")?; |
| 107 | + |
| 108 | + if max_circuit_length == coords.len() { |
| 109 | + ensure!(p1 != 0, "solved part 2 before part 1?"); |
| 110 | + p2 = b1.x * b2.x; |
| 111 | + break; |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + Ok((p1, p2)) |
| 116 | +} |
| 117 | + |
| 118 | +pub fn run_2025_08(inp: &str) -> Result<String> { |
| 119 | + let (p1, p2) = calculate::<1000>(inp)?; |
| 120 | + Ok(format!("{p1}\n{p2}")) |
| 121 | +} |
| 122 | + |
| 123 | +#[cfg(test)] |
| 124 | +mod tests { |
| 125 | + use super::*; |
| 126 | + |
| 127 | + const EXAMPLE_DATA: &str = include_str!("../inputs/examples/2025_08"); |
| 128 | + const REAL_DATA: &str = include_str!("../inputs/real/2025_08"); |
| 129 | + |
| 130 | + #[test] |
| 131 | + fn test_example_p1() { |
| 132 | + assert_eq!(calculate::<10>(EXAMPLE_DATA).unwrap().0, 40); |
| 133 | + } |
| 134 | + |
| 135 | + #[test] |
| 136 | + fn test_example_p2() { |
| 137 | + assert_eq!(calculate::<10>(EXAMPLE_DATA).unwrap().1, 25272); |
| 138 | + } |
| 139 | + |
| 140 | + #[test] |
| 141 | + fn test_real_p1() { |
| 142 | + assert_eq!(calculate::<1000>(REAL_DATA).unwrap().0, 96672); |
| 143 | + } |
| 144 | + |
| 145 | + #[test] |
| 146 | + fn test_real_p2() { |
| 147 | + assert_eq!(calculate::<1000>(REAL_DATA).unwrap().1, 22517595); |
| 148 | + } |
| 149 | +} |
0 commit comments