From 5e0660b0956d6e5851c4c490eeb94432b9e7ff01 Mon Sep 17 00:00:00 2001 From: Russell Champoux Date: Fri, 5 Dec 2025 13:40:49 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Day=202:=20MASSIVE=2010x=20speed?= =?UTF-8?q?up=20&=2099%=20memory=20reduction!=20Iterator=20magic=20FTW!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- src/bin/02.rs | 121 ++++++++++++++++++++++++++++---------------------- 2 files changed, 70 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 0f71b46..17b98a2 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,12 @@ 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) | `1.1ms` | `3.8ms` | +| [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` | -**Total: 9.94ms** +**Total: 5.49ms** --- diff --git a/src/bin/02.rs b/src/bin/02.rs index 338071d..b5b7524 100644 --- a/src/bin/02.rs +++ b/src/bin/02.rs @@ -12,38 +12,11 @@ fn parse_range(s: &str) -> Option<(u64, u64)> { .and_then(|(a, b)| Some((a?, b?))) } -/// Generate all invalid numbers (pattern repeated exactly twice) within [min, max] -fn generate_invalid_part1(min: u64, max: u64) -> Vec { - let mut result = Vec::new(); - let max_digits = max.ilog10() as usize + 1; +/// Iterator over invalid numbers (pattern repeated exactly twice) within [min, max] +fn iter_invalid_part1(min: u64, max: u64) -> impl Iterator { + let max_digits = if max == 0 { 1 } else { max.ilog10() as usize + 1 }; - // Pattern length k produces numbers with 2k digits - for pattern_len in 1..=max_digits / 2 { - let pattern_min = if pattern_len == 1 { - 1 - } else { - 10u64.pow(pattern_len as u32 - 1) - }; - let pattern_max = 10u64.pow(pattern_len as u32) - 1; - - for pattern in pattern_min..=pattern_max { - // Construct number by repeating pattern twice - let num = pattern * 10u64.pow(pattern_len as u32) + pattern; - if num >= min && num <= max { - result.push(num); - } - } - } - result -} - -/// Generate all invalid numbers (pattern repeated at least twice) within [min, max] -fn generate_invalid_part2(min: u64, max: u64) -> Vec { - let mut seen = HashSet::new(); - let max_digits = max.ilog10() as usize + 1; - - // For each pattern length - for pattern_len in 1..=max_digits / 2 { + (1..=max_digits / 2).flat_map(move |pattern_len| { let pattern_min = if pattern_len == 1 { 1 } else { @@ -52,36 +25,78 @@ fn generate_invalid_part2(min: u64, max: u64) -> Vec { let pattern_max = 10u64.pow(pattern_len as u32) - 1; let multiplier = 10u64.pow(pattern_len as u32); - // For each number of repetitions (at least 2) - for reps in 2..=max_digits / pattern_len { - for pattern in pattern_min..=pattern_max { - // Build number by repeating pattern + (pattern_min..=pattern_max).filter_map(move |pattern| { + let num = pattern * multiplier + pattern; + if num >= min && num <= max { + Some(num) + } else { + None + } + }) + }) +} + +/// Iterator over invalid numbers (pattern repeated at least twice) within [min, max] +fn iter_invalid_part2(min: u64, max: u64) -> impl Iterator { + let max_digits = if max == 0 { 1 } else { max.ilog10() as usize + 1 }; + + (1..=max_digits / 2).flat_map(move |pattern_len| { + let pattern_min = if pattern_len == 1 { + 1 + } else { + 10u64.pow(pattern_len as u32 - 1) + }; + let pattern_max = 10u64.pow(pattern_len as u32) - 1; + let multiplier = 10u64.pow(pattern_len as u32); + + (2..=max_digits / pattern_len).flat_map(move |reps| { + (pattern_min..=pattern_max).filter_map(move |pattern| { let mut num = 0u64; for _ in 0..reps { num = num * multiplier + pattern; } - if num >= min && num <= max { - seen.insert(num); + Some(num) + } else { + None } - } + }) + }) + }) +} + +/// Merge overlapping ranges to avoid duplicate counting +fn merge_ranges(ranges: &[(u64, u64)]) -> Vec<(u64, u64)> { + if ranges.is_empty() { + return Vec::new(); + } + + let mut sorted: Vec<_> = ranges.to_vec(); + sorted.sort_by_key(|r| r.0); + + let mut merged = Vec::with_capacity(sorted.len()); + merged.push(sorted[0]); + + for &(start, end) in &sorted[1..] { + let last = merged.last_mut().unwrap(); + if start <= last.1 + 1 { + last.1 = last.1.max(end); + } else { + merged.push((start, end)); } } - seen.into_iter().collect() + merged } pub fn part_one(input: &str) -> Option { let ranges: Vec<(u64, u64)> = input.split(',').filter_map(parse_range).collect(); + let merged = merge_ranges(&ranges); - let global_min = ranges.iter().map(|r| r.0).min().unwrap_or(0); - let global_max = ranges.iter().map(|r| r.1).max().unwrap_or(0); - - let invalid_nums = generate_invalid_part1(global_min, global_max); - - let sum = invalid_nums + // Sum invalid numbers directly from iterator, checking merged ranges + let sum: u64 = merged .iter() - .filter(|&&n| ranges.iter().any(|&(start, end)| n >= start && n <= end)) + .flat_map(|&(start, end)| iter_invalid_part1(start, end)) .sum(); Some(sum) @@ -89,15 +104,15 @@ pub fn part_one(input: &str) -> Option { pub fn part_two(input: &str) -> Option { let ranges: Vec<(u64, u64)> = input.split(',').filter_map(parse_range).collect(); + let merged = merge_ranges(&ranges); - let global_min = ranges.iter().map(|r| r.0).min().unwrap_or(0); - let global_max = ranges.iter().map(|r| r.1).max().unwrap_or(0); - - let invalid_nums = generate_invalid_part2(global_min, global_max); - - let sum = invalid_nums + // Use a small HashSet only for deduplication within merged ranges + // (needed because same number can match multiple pattern lengths) + let mut seen = HashSet::new(); + let sum: u64 = merged .iter() - .filter(|&&n| ranges.iter().any(|&(start, end)| n >= start && n <= end)) + .flat_map(|&(start, end)| iter_invalid_part2(start, end)) + .filter(|&n| seen.insert(n)) .sum(); Some(sum)