mirror of
https://github.com/RussellChamp/advent-of-code-2025-cursor.git
synced 2026-10-27 23:52:34 +00:00
Optimize Days 1-8: 41% faster overall (39ms → 23ms)
This commit is contained in:
1 parent
6ff1349679
commit
b067baac3d
10 files changed
+229
-183
No files matched your search
Generated
+3
-2
@@ -26,6 +26,7 @@ dependencies = [
|
||||
"itertools",
|
||||
"pico-args",
|
||||
"rayon",
|
||||
"smallvec",
|
||||
"tinyjson",
|
||||
]
|
||||
|
||||
@@ -422,9 +423,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.2"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
|
||||
@@ -27,6 +27,7 @@ dhat = { version = "0.3.3", optional = true }
|
||||
itertools = "0.14.0"
|
||||
pico-args = "0.5.0"
|
||||
rayon = "1.11.0"
|
||||
smallvec = "1.15.1"
|
||||
tinyjson = "2.5.1"
|
||||
|
||||
# Solution dependencies
|
||||
@@ -11,16 +11,16 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www.
|
||||
|
||||
| Day | Part 1 | Part 2 |
|
||||
| :---: | :---: | :---: |
|
||||
| [Day 1](./src/bin/01.rs) | `102.5µs` | `123.5µs` |
|
||||
| [Day 2](./src/bin/02.rs) | `131.1µs` | `314.3µs` |
|
||||
| [Day 3](./src/bin/03.rs) | `85.1µs` | `121.6µs` |
|
||||
| [Day 4](./src/bin/04.rs) | `244.2µs` | `4.1ms` |
|
||||
| [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` |
|
||||
| [Day 1](./src/bin/01.rs) | `93.9µs` | `113.6µs` |
|
||||
| [Day 2](./src/bin/02.rs) | `125.7µs` | `330.4µs` |
|
||||
| [Day 3](./src/bin/03.rs) | `118.8µs` | `108.4µs` |
|
||||
| [Day 4](./src/bin/04.rs) | `160.6µs` | `838.6µs` |
|
||||
| [Day 5](./src/bin/05.rs) | `53.3µs` | `44.4µs` |
|
||||
| [Day 6](./src/bin/06.rs) | `118.7µs` | `124.8µs` |
|
||||
| [Day 7](./src/bin/07.rs) | `91.8µs` | `72.4µs` |
|
||||
| [Day 8](./src/bin/08.rs) | `11.2ms` | `9.8ms` |
|
||||
|
||||
**Total: 39.48ms**
|
||||
**Total: 23.40ms**
|
||||
<!--- benchmarking table --->
|
||||
|
||||
---
|
||||
|
||||
+2
-4
@@ -1,5 +1,4 @@
|
||||
use itertools::Itertools;
|
||||
use std::collections::HashSet;
|
||||
|
||||
advent_of_code::solution!(2);
|
||||
|
||||
@@ -106,13 +105,12 @@ pub fn part_two(input: &str) -> Option<u64> {
|
||||
let ranges: Vec<(u64, u64)> = input.split(',').filter_map(parse_range).collect();
|
||||
let merged = merge_ranges(&ranges);
|
||||
|
||||
// Use a small HashSet only for deduplication within merged ranges
|
||||
// Use itertools::unique() for deduplication
|
||||
// (needed because same number can match multiple pattern lengths)
|
||||
let mut seen = HashSet::new();
|
||||
let sum: u64 = merged
|
||||
.iter()
|
||||
.flat_map(|&(start, end)| iter_invalid_part2(start, end))
|
||||
.filter(|&n| seen.insert(n))
|
||||
.unique()
|
||||
.sum();
|
||||
|
||||
Some(sum)
|
||||
|
||||
+8
-6
@@ -1,12 +1,14 @@
|
||||
use smallvec::SmallVec;
|
||||
|
||||
advent_of_code::solution!(3);
|
||||
|
||||
/// Find the maximum 2-digit number by selecting exactly 2 batteries from a bank
|
||||
fn max_joltage_2(bank: &[u8]) -> u64 {
|
||||
fn max_joltage_2(bank: &SmallVec<[u8; 32]>) -> u64 {
|
||||
let n = bank.len();
|
||||
|
||||
// Precompute suffix maximums: suffix_max[i] = max digit from position i to end
|
||||
// This avoids O(n) scan for each position, making overall O(n) instead of O(n²)
|
||||
let mut suffix_max = vec![0u8; n];
|
||||
let mut suffix_max: SmallVec<[u8; 32]> = SmallVec::from_elem(0, n);
|
||||
suffix_max[n - 1] = bank[n - 1];
|
||||
for i in (0..n - 1).rev() {
|
||||
suffix_max[i] = bank[i].max(suffix_max[i + 1]);
|
||||
@@ -32,8 +34,8 @@ pub fn part_one(input: &str) -> Option<u64> {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert line to digits
|
||||
let digits: Vec<u8> = line
|
||||
// Convert line to digits using SmallVec (avoids heap allocation for small inputs)
|
||||
let digits: SmallVec<[u8; 32]> = line
|
||||
.chars()
|
||||
.map(|c| c.to_digit(10).unwrap() as u8)
|
||||
.collect();
|
||||
@@ -44,7 +46,7 @@ pub fn part_one(input: &str) -> Option<u64> {
|
||||
}
|
||||
|
||||
/// Find the maximum k-digit number by selecting exactly k batteries from a bank
|
||||
fn max_joltage_k(bank: &[u8], k: usize) -> u64 {
|
||||
fn max_joltage_k(bank: &SmallVec<[u8; 32]>, k: usize) -> u64 {
|
||||
let n = bank.len();
|
||||
let mut result = 0u64;
|
||||
let mut start = 0;
|
||||
@@ -80,7 +82,7 @@ pub fn part_two(input: &str) -> Option<u64> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let digits: Vec<u8> = line
|
||||
let digits: SmallVec<[u8; 32]> = line
|
||||
.chars()
|
||||
.map(|c| c.to_digit(10).unwrap() as u8)
|
||||
.collect();
|
||||
|
||||
+81
-69
@@ -1,72 +1,54 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
advent_of_code::solution!(4);
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum Cell {
|
||||
Empty,
|
||||
Paper,
|
||||
}
|
||||
|
||||
impl Cell {
|
||||
fn from_char(c: char) -> Self {
|
||||
match c {
|
||||
'@' => Cell::Paper,
|
||||
_ => Cell::Empty,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_paper(self) -> bool {
|
||||
self == Cell::Paper
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse input into a grid of cells
|
||||
fn parse_grid(input: &str) -> Vec<Vec<Cell>> {
|
||||
/// Parse input into a grid of booleans (true = paper)
|
||||
fn parse_grid(input: &str) -> Vec<Vec<bool>> {
|
||||
input
|
||||
.lines()
|
||||
.filter(|line| !line.trim().is_empty())
|
||||
.map(|line| line.chars().map(Cell::from_char).collect())
|
||||
.map(|line| line.chars().map(|c| c == '@').collect())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Count adjacent paper rolls (8 directions) for a given position
|
||||
fn count_neighbors(grid: &[Vec<Cell>], row: usize, col: usize) -> usize {
|
||||
let rows = grid.len() as i32;
|
||||
let cols = grid[0].len() as i32;
|
||||
let mut count = 0;
|
||||
/// Get valid neighbors for a position
|
||||
fn get_neighbors(row: usize, col: usize, rows: usize, cols: usize) -> impl Iterator<Item = (usize, usize)> {
|
||||
const DELTAS: [(i32, i32); 8] = [
|
||||
(-1, -1), (-1, 0), (-1, 1),
|
||||
(0, -1), (0, 1),
|
||||
(1, -1), (1, 0), (1, 1),
|
||||
];
|
||||
|
||||
for dr in -1..=1 {
|
||||
for dc in -1..=1 {
|
||||
if dr == 0 && dc == 0 {
|
||||
continue;
|
||||
}
|
||||
let nr = row as i32 + dr;
|
||||
let nc = col as i32 + dc;
|
||||
if nr >= 0
|
||||
&& nr < rows
|
||||
&& nc >= 0
|
||||
&& nc < cols
|
||||
&& grid[nr as usize][nc as usize].is_paper()
|
||||
{
|
||||
count += 1;
|
||||
}
|
||||
DELTAS.into_iter().filter_map(move |(dr, dc)| {
|
||||
let nr = row as i32 + dr;
|
||||
let nc = col as i32 + dc;
|
||||
if nr >= 0 && nr < rows as i32 && nc >= 0 && nc < cols as i32 {
|
||||
Some((nr as usize, nc as usize))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
count
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if a paper roll at (row, col) is accessible (fewer than 4 neighbors)
|
||||
fn is_accessible(grid: &[Vec<Cell>], row: usize, col: usize) -> bool {
|
||||
grid[row][col].is_paper() && count_neighbors(grid, row, col) < 4
|
||||
/// Count adjacent paper rolls for a given position
|
||||
fn count_neighbors(grid: &[Vec<bool>], row: usize, col: usize) -> u8 {
|
||||
let rows = grid.len();
|
||||
let cols = grid[0].len();
|
||||
get_neighbors(row, col, rows, cols)
|
||||
.filter(|&(nr, nc)| grid[nr][nc])
|
||||
.count() as u8
|
||||
}
|
||||
|
||||
pub fn part_one(input: &str) -> Option<u64> {
|
||||
let grid = parse_grid(input);
|
||||
let mut accessible = 0u64;
|
||||
if grid.is_empty() {
|
||||
return Some(0);
|
||||
}
|
||||
|
||||
let mut accessible = 0u64;
|
||||
for row in 0..grid.len() {
|
||||
for col in 0..grid[0].len() {
|
||||
if is_accessible(&grid, row, col) {
|
||||
if grid[row][col] && count_neighbors(&grid, row, col) < 4 {
|
||||
accessible += 1;
|
||||
}
|
||||
}
|
||||
@@ -77,31 +59,61 @@ pub fn part_one(input: &str) -> Option<u64> {
|
||||
|
||||
pub fn part_two(input: &str) -> Option<u64> {
|
||||
let mut grid = parse_grid(input);
|
||||
if grid.is_empty() {
|
||||
return Some(0);
|
||||
}
|
||||
|
||||
let rows = grid.len();
|
||||
let cols = grid[0].len();
|
||||
|
||||
// Pre-compute neighbor counts for all cells
|
||||
let mut neighbor_counts: Vec<Vec<u8>> = vec![vec![0; cols]; rows];
|
||||
for row in 0..rows {
|
||||
for col in 0..cols {
|
||||
if grid[row][col] {
|
||||
neighbor_counts[row][col] = count_neighbors(&grid, row, col);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize queue with all accessible cells (paper with < 4 neighbors)
|
||||
let mut queue: VecDeque<(usize, usize)> = VecDeque::new();
|
||||
let mut in_queue: Vec<Vec<bool>> = vec![vec![false; cols]; rows];
|
||||
|
||||
for row in 0..rows {
|
||||
for col in 0..cols {
|
||||
if grid[row][col] && neighbor_counts[row][col] < 4 {
|
||||
queue.push_back((row, col));
|
||||
in_queue[row][col] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut total_removed = 0u64;
|
||||
|
||||
loop {
|
||||
// Find all accessible rolls (fewer than 4 neighbors)
|
||||
let mut to_remove: Vec<(usize, usize)> = Vec::new();
|
||||
while let Some((row, col)) = queue.pop_front() {
|
||||
in_queue[row][col] = false;
|
||||
|
||||
for row in 0..grid.len() {
|
||||
for col in 0..grid[0].len() {
|
||||
if is_accessible(&grid, row, col) {
|
||||
to_remove.push((row, col));
|
||||
// Skip if already removed or no longer accessible
|
||||
if !grid[row][col] || neighbor_counts[row][col] >= 4 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove this cell
|
||||
grid[row][col] = false;
|
||||
total_removed += 1;
|
||||
|
||||
// Update neighbor counts for adjacent cells and potentially add them to queue
|
||||
for (nr, nc) in get_neighbors(row, col, rows, cols) {
|
||||
if grid[nr][nc] {
|
||||
neighbor_counts[nr][nc] = neighbor_counts[nr][nc].saturating_sub(1);
|
||||
// If this cell became accessible and isn't already queued, add it
|
||||
if neighbor_counts[nr][nc] < 4 && !in_queue[nr][nc] {
|
||||
queue.push_back((nr, nc));
|
||||
in_queue[nr][nc] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no rolls are accessible, we're done
|
||||
if to_remove.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Remove all accessible rolls
|
||||
for (row, col) in &to_remove {
|
||||
grid[*row][*col] = Cell::Empty;
|
||||
}
|
||||
|
||||
total_removed += to_remove.len() as u64;
|
||||
}
|
||||
|
||||
Some(total_removed)
|
||||
|
||||
+40
-35
@@ -1,56 +1,61 @@
|
||||
advent_of_code::solution!(5);
|
||||
|
||||
/// A range of fresh ingredient IDs (inclusive)
|
||||
#[derive(Clone)]
|
||||
struct FreshRange {
|
||||
start: u64,
|
||||
end: u64,
|
||||
}
|
||||
|
||||
impl FreshRange {
|
||||
fn contains(&self, id: u64) -> bool {
|
||||
id >= self.start && id <= self.end
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the input into fresh ranges and available IDs
|
||||
/// Parse the input into fresh ranges and available IDs without extra allocations
|
||||
fn parse_input(input: &str) -> (Vec<FreshRange>, Vec<u64>) {
|
||||
// Normalize line endings and split on blank line
|
||||
let normalized = input.replace("\r\n", "\n");
|
||||
let mut sections = normalized.split("\n\n");
|
||||
let mut ranges = Vec::new();
|
||||
let mut ids = Vec::new();
|
||||
let mut in_ids_section = false;
|
||||
|
||||
// Parse fresh ingredient ID ranges
|
||||
let ranges_section = sections.next().unwrap_or("");
|
||||
let ranges: Vec<FreshRange> = ranges_section
|
||||
.lines()
|
||||
.filter(|line| !line.trim().is_empty())
|
||||
.map(|line| {
|
||||
let mut parts = line.split('-');
|
||||
let start: u64 = parts.next().unwrap().parse().unwrap();
|
||||
let end: u64 = parts.next().unwrap().parse().unwrap();
|
||||
FreshRange { start, end }
|
||||
})
|
||||
.collect();
|
||||
for line in input.lines() {
|
||||
let line = line.trim().trim_end_matches('\r');
|
||||
if line.is_empty() {
|
||||
in_ids_section = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse available ingredient IDs
|
||||
let ids_section = sections.next().unwrap_or("");
|
||||
let ids: Vec<u64> = ids_section
|
||||
.lines()
|
||||
.filter(|line| !line.trim().is_empty())
|
||||
.map(|line| line.trim().parse().unwrap())
|
||||
.collect();
|
||||
if in_ids_section {
|
||||
if let Ok(id) = line.parse() {
|
||||
ids.push(id);
|
||||
}
|
||||
} else if let Some((start_str, end_str)) = line.split_once('-') {
|
||||
if let (Ok(start), Ok(end)) = (start_str.parse(), end_str.parse()) {
|
||||
ranges.push(FreshRange { start, end });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(ranges, ids)
|
||||
}
|
||||
|
||||
/// Check if an ingredient ID is fresh (falls within any range)
|
||||
fn is_fresh(id: u64, ranges: &[FreshRange]) -> bool {
|
||||
ranges.iter().any(|range| range.contains(id))
|
||||
/// Check if an ingredient ID is fresh using binary search on sorted ranges
|
||||
fn is_fresh_binary(id: u64, sorted_ranges: &[FreshRange]) -> bool {
|
||||
// Binary search to find a range that might contain id
|
||||
// Find the rightmost range where start <= id
|
||||
let idx = sorted_ranges.partition_point(|r| r.start <= id);
|
||||
if idx == 0 {
|
||||
return false;
|
||||
}
|
||||
// Check if the range at idx-1 contains id
|
||||
sorted_ranges[idx - 1].end >= id
|
||||
}
|
||||
|
||||
pub fn part_one(input: &str) -> Option<u64> {
|
||||
let (ranges, ids) = parse_input(input);
|
||||
let (mut ranges, ids) = parse_input(input);
|
||||
|
||||
let fresh_count = ids.iter().filter(|&&id| is_fresh(id, &ranges)).count();
|
||||
// Sort ranges by start for binary search
|
||||
ranges.sort_unstable_by_key(|r| r.start);
|
||||
|
||||
let fresh_count = ids
|
||||
.iter()
|
||||
.filter(|&&id| is_fresh_binary(id, &ranges))
|
||||
.count();
|
||||
|
||||
Some(fresh_count as u64)
|
||||
}
|
||||
@@ -62,7 +67,7 @@ fn count_unique_fresh_ids(mut ranges: Vec<FreshRange>) -> u64 {
|
||||
}
|
||||
|
||||
// Sort ranges by start
|
||||
ranges.sort_by_key(|r| r.start);
|
||||
ranges.sort_unstable_by_key(|r| r.start);
|
||||
|
||||
// Merge overlapping ranges
|
||||
let mut merged: Vec<FreshRange> = Vec::new();
|
||||
|
||||
+11
-6
@@ -116,15 +116,20 @@ fn solve_worksheet_cephalopod(input: &str) -> u64 {
|
||||
.unwrap_or('+');
|
||||
|
||||
// Read columns right-to-left, each column forms a number
|
||||
// Parse digits directly to u64 without String allocation
|
||||
let numbers: Vec<u64> = (start..end)
|
||||
.rev()
|
||||
.filter_map(|col| {
|
||||
let digits: String = number_rows
|
||||
.iter()
|
||||
.map(|row| row[col])
|
||||
.filter(|c| c.is_ascii_digit())
|
||||
.collect();
|
||||
digits.parse().ok()
|
||||
let mut num = 0u64;
|
||||
let mut has_digits = false;
|
||||
for row in number_rows {
|
||||
let c = row[col];
|
||||
if c.is_ascii_digit() {
|
||||
num = num * 10 + (c as u64 - '0' as u64);
|
||||
has_digits = true;
|
||||
}
|
||||
}
|
||||
has_digits.then_some(num)
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
+34
-26
@@ -1,5 +1,3 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
advent_of_code::solution!(7);
|
||||
|
||||
/// Parse the grid and find the starting column
|
||||
@@ -19,7 +17,7 @@ fn parse_grid(input: &str) -> (Vec<Vec<char>>, usize) {
|
||||
}
|
||||
|
||||
/// Simulate tachyon beams through the manifold and count splits
|
||||
/// Beams merge when they occupy the same column (HashSet)
|
||||
/// Uses Vec<bool> instead of HashSet for beam positions
|
||||
fn count_splits(input: &str) -> u64 {
|
||||
let (grid, start_col) = parse_grid(input);
|
||||
if grid.is_empty() {
|
||||
@@ -27,33 +25,41 @@ fn count_splits(input: &str) -> u64 {
|
||||
}
|
||||
|
||||
let width = grid[0].len();
|
||||
let mut beams: HashSet<usize> = HashSet::new();
|
||||
beams.insert(start_col);
|
||||
let mut beams = vec![false; width];
|
||||
let mut new_beams = vec![false; width];
|
||||
beams[start_col] = true;
|
||||
|
||||
let mut split_count: u64 = 0;
|
||||
|
||||
for row in grid.iter().skip(1) {
|
||||
let mut new_beams: HashSet<usize> = HashSet::new();
|
||||
new_beams.fill(false);
|
||||
let mut has_beams = false;
|
||||
|
||||
for &col in &beams {
|
||||
for col in 0..width {
|
||||
if !beams[col] {
|
||||
continue;
|
||||
}
|
||||
match row[col] {
|
||||
'^' => {
|
||||
split_count += 1;
|
||||
if col > 0 {
|
||||
new_beams.insert(col - 1);
|
||||
new_beams[col - 1] = true;
|
||||
has_beams = true;
|
||||
}
|
||||
if col + 1 < width {
|
||||
new_beams.insert(col + 1);
|
||||
new_beams[col + 1] = true;
|
||||
has_beams = true;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
new_beams.insert(col);
|
||||
new_beams[col] = true;
|
||||
has_beams = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beams = new_beams;
|
||||
if beams.is_empty() {
|
||||
std::mem::swap(&mut beams, &mut new_beams);
|
||||
if !has_beams {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -62,7 +68,7 @@ fn count_splits(input: &str) -> u64 {
|
||||
}
|
||||
|
||||
/// Count the number of unique timelines (paths) through the manifold
|
||||
/// Each particle at a position represents independent timelines - they don't merge
|
||||
/// Uses Vec<u64> instead of HashMap for particle counts
|
||||
fn count_timelines(input: &str) -> u64 {
|
||||
let (grid, start_col) = parse_grid(input);
|
||||
if grid.is_empty() {
|
||||
@@ -70,40 +76,42 @@ fn count_timelines(input: &str) -> u64 {
|
||||
}
|
||||
|
||||
let width = grid[0].len();
|
||||
|
||||
// Track number of particles (timelines) at each column position
|
||||
let mut particles: HashMap<usize, u64> = HashMap::new();
|
||||
particles.insert(start_col, 1);
|
||||
let mut particles = vec![0u64; width];
|
||||
let mut new_particles = vec![0u64; width];
|
||||
particles[start_col] = 1;
|
||||
|
||||
for row in grid.iter().skip(1) {
|
||||
let mut new_particles: HashMap<usize, u64> = HashMap::new();
|
||||
new_particles.fill(0);
|
||||
|
||||
for (&col, &count) in &particles {
|
||||
for col in 0..width {
|
||||
let count = particles[col];
|
||||
if count == 0 {
|
||||
continue;
|
||||
}
|
||||
match row[col] {
|
||||
'^' => {
|
||||
// Each particle splits into 2 timelines (left and right)
|
||||
if col > 0 {
|
||||
*new_particles.entry(col - 1).or_insert(0) += count;
|
||||
new_particles[col - 1] += count;
|
||||
}
|
||||
if col + 1 < width {
|
||||
*new_particles.entry(col + 1).or_insert(0) += count;
|
||||
new_particles[col + 1] += count;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Particle continues straight down
|
||||
*new_particles.entry(col).or_insert(0) += count;
|
||||
new_particles[col] += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
particles = new_particles;
|
||||
if particles.is_empty() {
|
||||
std::mem::swap(&mut particles, &mut new_particles);
|
||||
if particles.iter().all(|&c| c == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Sum all particles - each represents a unique timeline
|
||||
particles.values().sum()
|
||||
particles.iter().sum()
|
||||
}
|
||||
|
||||
pub fn part_one(input: &str) -> Option<u64> {
|
||||
|
||||
+40
-26
@@ -1,4 +1,4 @@
|
||||
use rayon::slice::ParallelSliceMut;
|
||||
use rayon::prelude::*;
|
||||
|
||||
advent_of_code::solution!(8);
|
||||
|
||||
@@ -82,42 +82,47 @@ fn distance_squared(a: (i64, i64, i64), b: (i64, i64, i64)) -> i64 {
|
||||
dx * dx + dy * dy + dz * dz
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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();
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Generate all pairs with distances in parallel
|
||||
fn generate_pairs_parallel(coords: &[(i64, i64, i64)]) -> Vec<(i64, usize, usize)> {
|
||||
let n = coords.len();
|
||||
|
||||
// Generate all pairs with distances
|
||||
let mut pairs: Vec<(i64, usize, usize)> = Vec::with_capacity(n * (n - 1) / 2);
|
||||
for i in 0..n {
|
||||
for j in (i + 1)..n {
|
||||
let dist = distance_squared(coords[i], coords[j]);
|
||||
pairs.push((dist, i, j));
|
||||
}
|
||||
}
|
||||
|
||||
// Parallel sort by distance using rayon
|
||||
pairs.par_sort_unstable_by_key(|&(dist, _, _)| dist);
|
||||
|
||||
(coords, pairs)
|
||||
(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> {
|
||||
let (coords, pairs) = generate_sorted_pairs(input);
|
||||
let coords = parse_coords(input);
|
||||
let n = coords.len();
|
||||
if n == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut uf = UnionFind::new(n);
|
||||
let mut pairs = generate_pairs_parallel(&coords);
|
||||
|
||||
// Process first 1000 connections
|
||||
for &(_, i, j) in pairs.iter().take(1000) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
let mut uf = UnionFind::new(n);
|
||||
for &(_, i, j) in &pairs {
|
||||
uf.union(i, j);
|
||||
}
|
||||
|
||||
@@ -129,12 +134,17 @@ pub fn part_one(input: &str) -> Option<u64> {
|
||||
}
|
||||
|
||||
pub fn part_two(input: &str) -> Option<u64> {
|
||||
let (coords, pairs) = generate_sorted_pairs(input);
|
||||
let coords = parse_coords(input);
|
||||
let n = coords.len();
|
||||
if n <= 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut pairs = generate_pairs_parallel(&coords);
|
||||
|
||||
// Full parallel sort needed for part 2 (process in order)
|
||||
pairs.par_sort_unstable_by_key(|&(dist, _, _)| dist);
|
||||
|
||||
let mut uf = UnionFind::new(n);
|
||||
let mut num_circuits = n;
|
||||
let mut last_connection: Option<(usize, usize)> = None;
|
||||
@@ -160,10 +170,14 @@ mod tests {
|
||||
#[test]
|
||||
fn test_part_one() {
|
||||
// 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 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);
|
||||
|
||||
let mut uf = UnionFind::new(n);
|
||||
for &(_, i, j) in pairs.iter().take(10) {
|
||||
for &(_, i, j) in &pairs {
|
||||
uf.union(i, j);
|
||||
}
|
||||
let mut sizes = uf.get_circuit_sizes();
|
||||
|
||||
Reference in new issue
Block a user