diff --git a/Cargo.toml b/Cargo.toml index 937cb49..13a76b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +num-bigint = "0.4.6" +num-integer = "0.1.46" +num-traits = "0.2.19" tokio = { version = "1.48.0", features = ["full"] } diff --git a/input/day02 b/input/day02 index e69de29..77035e8 100644 --- a/input/day02 +++ b/input/day02 @@ -0,0 +1 @@ +492410748-492568208,246-390,49-90,16-33,142410-276301,54304-107961,12792-24543,3434259704-3434457648,848156-886303,152-223,1303-1870,8400386-8519049,89742532-89811632,535853-567216,6608885-6724046,1985013826-1985207678,585591-731454,1-13,12067202-12233567,6533-10235,6259999-6321337,908315-972306,831-1296,406-824,769293-785465,3862-5652,26439-45395,95-136,747698990-747770821,984992-1022864,34-47,360832-469125,277865-333851,2281-3344,2841977-2953689,29330524-29523460 \ No newline at end of file diff --git a/input/day02_test1 b/input/day02_test1 index e69de29..b3dc7c4 100644 --- a/input/day02_test1 +++ b/input/day02_test1 @@ -0,0 +1,3 @@ +11-22,95-115,998-1012,1188511880-1188511890,222220-222224, +1698522-1698528,446443-446449,38593856-38593862,565653-565659, +824824821-824824827,2121212118-2121212124 \ No newline at end of file diff --git a/input/day02_test2 b/input/day02_test2 index e69de29..b3dc7c4 100644 --- a/input/day02_test2 +++ b/input/day02_test2 @@ -0,0 +1,3 @@ +11-22,95-115,998-1012,1188511880-1188511890,222220-222224, +1698522-1698528,446443-446449,38593856-38593862,565653-565659, +824824821-824824827,2121212118-2121212124 \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 3b9e25d..f46e464 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ async fn main() -> Result<(), Box> { /* Uncomment these before the start of each day! */ let days: Vec> = vec![ Box::new(day01::Day01 {}), - // Box::new(day02::Day02 {}), + Box::new(day02::Day02 {}), // Box::new(day03::Day03 {}), // Box::new(day04::Day04 {}), // Box::new(day05::Day05 {}), diff --git a/src/solutions/day01.rs b/src/solutions/day01.rs index 5cd9d39..22a847d 100644 --- a/src/solutions/day01.rs +++ b/src/solutions/day01.rs @@ -25,7 +25,7 @@ impl Solution for Day01 { impl Day01 { fn count_zeros(input: &Vec, count_passing: bool) -> usize { // Dial starts at 50 - let mut pos: i64 = 50; + let mut pos: i32 = 50; let mut zeros: usize = 0; for line in input { @@ -37,7 +37,7 @@ impl Day01 { let mut chars = line.chars(); let dir = chars.next().expect("empty line"); let dist_str: String = chars.collect::().trim().to_string(); - let dist: i64 = dist_str.parse().expect("invalid distance number"); + let dist: i32 = dist_str.parse().expect("invalid distance number"); // Count any time that the dial moves past 0. if count_passing { diff --git a/src/solutions/day02.rs b/src/solutions/day02.rs index 3ca9181..52c1e23 100644 --- a/src/solutions/day02.rs +++ b/src/solutions/day02.rs @@ -1,20 +1,191 @@ use super::Solution; +use num_bigint::BigUint; +use num_integer::Integer; +use num_traits::{One, Zero}; +use std::collections::HashSet; pub struct Day02 {} impl Solution for Day02 { fn part1( &self, - _input: &mut Vec, + input: &mut Vec, ) -> Result, Box> { - Ok(Box::new("Ready")) + let s = input.join("\n"); + let ranges = self.parse_ranges(s.as_str()); + + // Determine maximum digits among all upper bounds + let overall_max = ranges + .iter() + .map(|(_, hi)| hi.clone()) + .max() + .unwrap_or_else(|| BigUint::zero()); + + let max_digits = self.digits_of(&overall_max); + + // Precompute 10^n for n up to max_digits + let mut pow10_cache: Vec = Vec::with_capacity(max_digits + 1); + for i in 0..=max_digits { + pow10_cache.push(self.pow10(i)); + } + + let mut total = BigUint::zero(); + + // For PART 1: + // Invalid number is exactly two blocks: x repeated twice. + // If x has k digits, the total has length 2k. + // Value = x * (10^k + 1) + for k in 1..=max_digits / 2 { + let pow10k = &pow10_cache[k]; + + // x ranges: k-digit numbers with no leading zero. + let x_min = if k == 1 { + BigUint::one() + } else { + pow10_cache[k - 1].clone() + }; + let x_max = pow10k - BigUint::one(); + + // multiplier = 10^k + 1 + let multiplier = pow10k + BigUint::one(); + + for (lo, hi) in &ranges { + if lo > hi { + continue; + } + + // Compute ceil(lo / multiplier) + let x_low = { + let (q, r) = lo.div_rem(&multiplier); + if r.is_zero() { q } else { q + BigUint::one() } + }; + + // Compute floor(hi / multiplier) + let x_high = hi / &multiplier; + + // Intersect x range with [x_min, x_max] + let a = if x_low < x_min { x_min.clone() } else { x_low }; + let b = if x_high > x_max { + x_max.clone() + } else { + x_high + }; + + if a > b { + continue; + } + + // count n = b - a + 1 + let n = &b - &a + BigUint::one(); + + // sum_x = n*(a+b)/2 + let ab = &a + &b; + let prod = &n * ab; + let sum_x = prod / 2u32; + + // Add multiplier * sum_x + total += &multiplier * sum_x; + } + } + Ok(Box::new(total)) } fn part2( &self, - _input: &mut Vec, + input: &mut Vec, ) -> Result, Box> { - Ok(Box::new("Ready")) + let s = input.join("\n"); + let ranges = self.parse_ranges(s.as_str()); + + // Compute limits + let overall_max = ranges + .iter() + .map(|(_, hi)| hi.clone()) + .max() + .unwrap_or_else(|| BigUint::zero()); + + let max_digits = self.digits_of(&overall_max); + + // Precompute powers of 10 + let mut pow10_cache = Vec::with_capacity(max_digits + 1); + for i in 0..=max_digits { + pow10_cache.push(self.pow10(i)); + } + + // The important fix: ALWAYS DEDUP + let mut invalid_ids = HashSet::::new(); + + // Consider total length L + for l in 2..=max_digits { + for k in 1..=l { + if l % k != 0 { + continue; + } + let r = l / k; + if r < 2 { + continue; + } + + let pow10k = &pow10_cache[k]; + let pow10l = &pow10_cache[l]; + + // Repetition multiplier: 111... (r groups of size k) + let numerator = pow10l - BigUint::one(); + let denom = pow10k - BigUint::one(); + if denom.is_zero() { + continue; + } + let multiplier = &numerator / &denom; + + // k-digit block roots + let x_min = if k == 1 { + BigUint::one() + } else { + pow10_cache[k - 1].clone() + }; + let x_max = pow10k - BigUint::one(); + + for (lo, hi) in &ranges { + if lo > hi { + continue; + } + + let x_low = { + let (q, rmd) = lo.div_rem(&multiplier); + if rmd.is_zero() { q } else { q + BigUint::one() } + }; + let x_high = hi / &multiplier; + + let a = if x_low < x_min { x_min.clone() } else { x_low }; + let b = if x_high > x_max { + x_max.clone() + } else { + x_high + }; + + if a > b { + continue; + } + + // Enumerate all x in [a, b] — this is small in practice + let mut x = a; + while x <= b { + let id = &x * &multiplier; + invalid_ids.insert(id); + + x += BigUint::one(); + } + } + } + } + + // Sum final unique invalid IDs + let mut total = BigUint::zero(); + for id in invalid_ids { + total += id; + } + + Ok(Box::new(total)) } fn get_day(&self) -> u8 { @@ -22,7 +193,42 @@ impl Solution for Day02 { } } -impl Day02 {} +impl Day02 { + fn pow10(&self, n: usize) -> BigUint { + let ten = BigUint::from(10u8); + ten.pow(n as u32) + } + + fn digits_of(&self, n: &BigUint) -> usize { + if n.is_zero() { + return 1; + } + n.to_string().len() + } + + fn parse_ranges(&self, s: &str) -> Vec<(BigUint, BigUint)> { + let mut out = Vec::new(); + for token in s.split(',') { + let t = token.trim(); + if t.is_empty() { + continue; + } + let parts: Vec<&str> = t.split('-').collect(); + if parts.len() != 2 { + eprintln!("Skipping malformed token: {}", t); + continue; + } + let a = parts[0].trim().parse::().expect("parse start"); + let b = parts[1].trim().parse::().expect("parse end"); + if a <= b { + out.push((a, b)); + } else { + out.push((b, a)); + } + } + out + } +} /// Test from puzzle input #[cfg(test)] @@ -44,7 +250,7 @@ mod test { .unwrap() .to_string(); - assert_eq!(answer, "Ready"); + assert_eq!(answer, "1227775554"); } #[test] @@ -61,6 +267,6 @@ mod test { .unwrap() .to_string(); - assert_eq!(answer, "Ready"); + assert_eq!(answer, "4174379265"); } }