⚡ Day 8: 2.2x FASTER with rayon parallel sorting! From 33ms to 15ms 💪

This commit is contained in:
Russell Champoux committed 2025-12-09 16:04:45 -05:00
1 parent ca302578c9
commit 6ff1349679
4 files changed
+104 -28

No files matched your search

Generated
+46
View File
@@ -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"
+1
View File
@@ -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
+2 -1
View File
@@ -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**
<!--- benchmarking table --->
---
+55 -27
View File
@@ -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<u64> {
/// 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<u64> {
.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<u64> {
}
}
// 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<u64> {
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<u64> {
solve(input, 1000)
Some(sizes.iter().take(3).map(|&s| s as u64).product())
}
pub fn part_two(input: &str) -> Option<u64> {
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));
}
}