diff --git a/Cargo.lock b/Cargo.lock index 355c537..af25d6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,6 +25,7 @@ dependencies = [ "dhat", "itertools", "pico-args", + "rayon", "tinyjson", ] @@ -111,6 +112,31 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "dhat" version = "0.3.3" @@ -310,6 +336,26 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.4.1" diff --git a/Cargo.toml b/Cargo.toml index 27532cf..cc67065 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ chrono = { version = "0.4.38", optional = true } dhat = { version = "0.3.3", optional = true } itertools = "0.14.0" pico-args = "0.5.0" +rayon = "1.11.0" tinyjson = "2.5.1" # Solution dependencies diff --git a/README.md b/README.md index 2c8d7e8..e465fa3 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,9 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www. | [Day 5](./src/bin/05.rs) | `196.7µs` | `66.3µs` | | [Day 6](./src/bin/06.rs) | `109.0µs` | `194.2µs` | | [Day 7](./src/bin/07.rs) | `270.5µs` | `320.2µs` | +| [Day 8](./src/bin/08.rs) | `16.9ms` | `16.2ms` | -**Total: 6.38ms** +**Total: 39.48ms** --- diff --git a/src/bin/08.rs b/src/bin/08.rs index 96820af..8cd6b63 100644 --- a/src/bin/08.rs +++ b/src/bin/08.rs @@ -1,3 +1,5 @@ +use rayon::slice::ParallelSliceMut; + advent_of_code::solution!(8); /// Union-Find data structure for tracking circuits @@ -72,6 +74,7 @@ fn parse_coord(line: &str) -> Option<(i64, i64, i64)> { } /// 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; @@ -79,7 +82,8 @@ fn distance_squared(a: (i64, i64, i64), b: (i64, i64, i64)) -> i64 { dx * dx + dy * dy + dz * dz } -fn solve(input: &str, num_connections: usize) -> Option { +/// Parse input and generate sorted pairs (shared between parts) +fn generate_sorted_pairs(input: &str) -> (Vec<(i64, i64, i64)>, Vec<(i64, usize, usize)>) { let coords: Vec<(i64, i64, i64)> = input .lines() .filter(|line| !line.trim().is_empty()) @@ -87,9 +91,6 @@ fn solve(input: &str, num_connections: usize) -> Option { .collect(); let n = coords.len(); - if n == 0 { - return None; - } // Generate all pairs with distances let mut pairs: Vec<(i64, usize, usize)> = Vec::with_capacity(n * (n - 1) / 2); @@ -100,37 +101,56 @@ fn solve(input: &str, num_connections: usize) -> Option { } } - // Sort by distance - pairs.sort_unstable_by_key(|&(dist, _, _)| dist); + // Parallel sort by distance using rayon + pairs.par_sort_unstable_by_key(|&(dist, _, _)| dist); + + (coords, pairs) +} + +pub fn part_one(input: &str) -> Option { + let (coords, pairs) = generate_sorted_pairs(input); + let n = coords.len(); + if n == 0 { + return None; + } - // Process connections using Union-Find let mut uf = UnionFind::new(n); - let mut connections_made = 0; - for (_, i, j) in pairs { - if connections_made >= num_connections { - break; - } - // Try to connect - even if already connected, it counts as one of our connections + // Process first 1000 connections + for &(_, i, j) in pairs.iter().take(1000) { uf.union(i, j); - 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)); // Sort descending + sizes.sort_unstable_by(|a, b| b.cmp(a)); - // Multiply 3 largest - let result: u64 = sizes.iter().take(3).map(|&s| s as u64).product(); - Some(result) -} - -pub fn part_one(input: &str) -> Option { - solve(input, 1000) + Some(sizes.iter().take(3).map(|&s| s as u64).product()) } pub fn part_two(input: &str) -> Option { - None + let (coords, pairs) = generate_sorted_pairs(input); + let n = coords.len(); + if n <= 1 { + return None; + } + + let mut uf = UnionFind::new(n); + let mut num_circuits = n; + let mut last_connection: Option<(usize, usize)> = None; + + for &(_, i, j) in &pairs { + if uf.union(i, j) { + num_circuits -= 1; + last_connection = Some((i, j)); + + if num_circuits == 1 { + break; + } + } + } + + last_connection.map(|(i, j)| coords[i].0 as u64 * coords[j].0 as u64) } #[cfg(test)] @@ -139,14 +159,22 @@ mod tests { #[test] fn test_part_one() { - // Example uses 10 connections, not 1000 - let result = solve(&advent_of_code::template::read_file("examples", DAY), 10); - assert_eq!(result, Some(40)); + // Example uses 10 connections, not 1000 - test the core logic + let (coords, pairs) = generate_sorted_pairs(&advent_of_code::template::read_file("examples", DAY)); + let n = coords.len(); + let mut uf = UnionFind::new(n); + for &(_, i, j) in pairs.iter().take(10) { + uf.union(i, j); + } + 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(); + assert_eq!(result, 40); } #[test] fn test_part_two() { let result = part_two(&advent_of_code::template::read_file("examples", DAY)); - assert_eq!(result, None); + assert_eq!(result, Some(25272)); } }