Advent of Code for 2025!

day 2: Fix the gift shop's database

+328
+8
examples/day_02_gift_shop.proptest-regressions
··· 1 + # Seeds for failure cases proptest has generated in the past. It is 2 + # automatically read and these particular cases re-run before any 3 + # novel cases are generated. 4 + # 5 + # It is recommended to check this file in to source control so that 6 + # everyone who runs the test benefits from these saved cases. 7 + cc 162969ac4b49f148171bb87379422306eeff964439b9413525aac38d4d347679 # shrinks to s = "𞹡" 8 + cc 49579348452c91933743f79fb03bd9688e9c20fde27f00ae2e10d9b1e1c115cf # shrinks to x = 1
+320
examples/day_02_gift_shop.rs
··· 1 + use std::ops::RangeInclusive; 2 + 3 + fn main() { 4 + aoc2025::run( 5 + "Day 2: Gift Shop", 6 + "inputs/day_02.txt", 7 + parse_input, 8 + part_1, 9 + part_2, 10 + ) 11 + .unwrap(); 12 + } 13 + 14 + fn parse_chunk(chunk: &str) -> Option<RangeInclusive<u64>> { 15 + let chunk = chunk.trim(); 16 + let (start, end) = chunk.find('-').map(|i| chunk.split_at(i))?; 17 + let start = start.parse::<u64>().ok(); 18 + let end = end.trim_start_matches('-').parse::<u64>().ok(); 19 + start.zip(end).map(|(start, end)| start..=end) 20 + } 21 + 22 + fn parse_input(input: &str) -> Vec<RangeInclusive<u64>> { 23 + (input.split(',')) 24 + .filter_map(|ch| parse_chunk(ch)) 25 + .collect::<Vec<_>>() 26 + } 27 + 28 + fn repeats_once(value: u64) -> bool { 29 + let value = value.to_string(); 30 + 31 + if value.len() % 2 != 0 { 32 + return false; 33 + } 34 + 35 + let (start, end) = value.split_at(value.len() / 2); 36 + start == end 37 + } 38 + 39 + fn factors(x: usize) -> Vec<usize> { 40 + (1..=x) 41 + .filter_map(|i| if x % i == 0 { Some(i) } else { None }) 42 + .collect::<Vec<_>>() 43 + } 44 + 45 + fn repeats_multiple_times(value: u64) -> bool { 46 + let value = value.to_string(); 47 + let value = value.as_bytes(); 48 + 49 + if value.len() <= 1 { 50 + return false; 51 + } 52 + 53 + for l in factors(value.len()) { 54 + if l == value.len() { 55 + continue; 56 + } 57 + 58 + let first = &value[0..l]; 59 + if value.chunks(l).all(|c| c == first) { 60 + return true; 61 + } 62 + } 63 + 64 + false 65 + } 66 + 67 + fn part_1(ranges: Vec<RangeInclusive<u64>>) -> u64 { 68 + (ranges.into_iter()) 69 + .map(|range| range.filter(|x| repeats_once(*x)).sum::<u64>()) 70 + .sum() 71 + } 72 + 73 + fn part_2(ranges: Vec<RangeInclusive<u64>>) -> u64 { 74 + (ranges.into_iter()) 75 + .map(|range| range.filter(|x| repeats_multiple_times(*x)).sum::<u64>()) 76 + .sum() 77 + } 78 + 79 + #[cfg(test)] 80 + mod tests { 81 + use std::ops::RangeInclusive; 82 + 83 + use super::*; 84 + use proptest::prelude::*; 85 + 86 + prop_compose! { 87 + fn chunk()( 88 + x in any::<u64>(), 89 + y in any::<u64>(), 90 + w0 in "\\s*", 91 + w1 in "\\s*" 92 + ) -> (RangeInclusive<u64>, String) { 93 + let start = x.min(y); 94 + let end = x.max(y); 95 + 96 + (start..=end, format!("{}{}-{}{}", w0, start, end, w1)) 97 + } 98 + } 99 + 100 + prop_compose! { 101 + fn input()(v in prop::collection::vec(chunk(), 1..100)) -> (Vec<RangeInclusive<u64>>, String) { 102 + let (ranges, chunks): (Vec<_>, Vec<_>) = v.into_iter().unzip(); 103 + (ranges, chunks.join(",")) 104 + } 105 + } 106 + 107 + mod parse_chunk { 108 + use super::*; 109 + 110 + proptest! { 111 + #[test] 112 + fn doesnt_crash(s in "\\PC+") { 113 + parse_chunk(s.as_str()); 114 + } 115 + 116 + #[test] 117 + fn parses_chunks(ch in chunk()) { 118 + let (expected, input) = ch; 119 + prop_assert_eq!(Some(expected), parse_chunk(&input)); 120 + } 121 + } 122 + } 123 + 124 + mod parse_input { 125 + use super::*; 126 + 127 + proptest! { 128 + #[test] 129 + fn doesnt_crash(s in "\\PC+") { 130 + parse_input(s.as_str()); 131 + } 132 + 133 + #[test] 134 + fn ignores_empty_chunks(s in ",+") { 135 + prop_assert_eq!(Vec::<RangeInclusive<u64>>::new(), parse_input(&s)); 136 + } 137 + 138 + #[test] 139 + fn parses_input_correctly(s in input()) { 140 + let (ranges, input) = s; 141 + prop_assert_eq!(ranges, parse_input(&input)); 142 + } 143 + } 144 + } 145 + 146 + mod repeats_once { 147 + use super::*; 148 + 149 + fn odd_length() -> impl Strategy<Value = u64> { 150 + any::<u64>().prop_filter("must be odd length", |x| x.to_string().len() % 2 != 0) 151 + } 152 + 153 + fn even_length_nonrepeating() -> impl Strategy<Value = u64> { 154 + any::<u64>() 155 + .prop_filter("must be even length", |x| x.to_string().len() % 2 == 0) 156 + .prop_filter("must be nonrepeating", |x| { 157 + let x = x.to_string(); 158 + let (a, b) = x.split_at(x.len() / 2); 159 + a != b 160 + }) 161 + } 162 + 163 + const MAX_SUBNUMBER: u64 = 1_844_674_407; 164 + 165 + prop_compose! { 166 + fn even_length_repeating()(x in 0_u64..MAX_SUBNUMBER) -> u64 { 167 + format!("{}{}", x, x).parse::<u64>().unwrap() 168 + } 169 + } 170 + 171 + proptest! { 172 + #[test] 173 + fn doesnt_crash(x in any::<u64>()) { 174 + repeats_once(x); 175 + } 176 + 177 + #[test] 178 + fn odd_lengths_cant_repeat(x in odd_length()) { 179 + prop_assert!(!repeats_once(x)); 180 + } 181 + 182 + #[test] 183 + fn detects_nonrepeating(x in even_length_nonrepeating()) { 184 + prop_assert!(!repeats_once(x)); 185 + } 186 + 187 + #[test] 188 + fn detects_repeating_once(x in even_length_repeating()) { 189 + prop_assert!(repeats_once(x)); 190 + } 191 + } 192 + } 193 + 194 + mod factors { 195 + use super::*; 196 + 197 + // We can test smaller ranges here because `factors` will only be used 198 + // on the length of the string representation of a number, not the 199 + // number itself. 200 + 201 + proptest! { 202 + #[test] 203 + fn doesnt_crash(x in 1_usize..=20) { 204 + factors(x); 205 + } 206 + 207 + #[test] 208 + fn always_includes_one(x in 1_usize..=20) { 209 + prop_assert!(factors(x).contains(&1)) 210 + } 211 + 212 + #[test] 213 + fn always_includes_input(x in 1_usize..=20) { 214 + prop_assert!(factors(x).contains(&x)) 215 + } 216 + 217 + #[test] 218 + fn factors_correctly(x in 1_usize..=10, y in 1_usize..=10) { 219 + let f = factors(x * y); 220 + prop_assert!(f.contains(&x)); 221 + prop_assert!(f.contains(&y)); 222 + } 223 + } 224 + } 225 + 226 + mod repeats_multiple_times { 227 + use proptest::string; 228 + 229 + use super::*; 230 + 231 + // Any string that repeats X times must be a multiple of that length 232 + // Therefore, find the factors of len(s) and get the substring chunks 233 + // If all are equal for any factor, return true 234 + 235 + const PRIME_LENGTHS: &[usize] = &[2, 3, 5, 7, 11, 13, 17, 19]; 236 + 237 + // Doesn't cover all nonrepeating numbers but that's a lot of work and 238 + // this should be good enough 239 + fn nonrepeating() -> impl Strategy<Value = u64> { 240 + prop::sample::select(PRIME_LENGTHS.to_vec()).prop_flat_map(|len| { 241 + let pattern = format!("[1-9][0-9]{{{}}}", len - 1); 242 + string::string_regex(pattern.as_str()) 243 + .unwrap() 244 + .prop_filter_map("not all digits identical", move |s| { 245 + let mut chars = s.chars(); 246 + let first = chars.next().unwrap(); 247 + if chars.all(|c| c == first) { 248 + return None; 249 + } 250 + 251 + s.parse::<u64>().ok() 252 + }) 253 + }) 254 + } 255 + 256 + fn repeating() -> impl Strategy<Value = u64> { 257 + (2_usize..10, 1_usize..10) 258 + .prop_filter("total length must fit within u64", |(x, y)| x * y < 20) 259 + .prop_flat_map(|(n, length)| { 260 + let pattern = match length { 261 + 1 => "[1-9]".to_string(), 262 + l => format!("[1-9][0-9]{{{}}}", l - 1), 263 + }; 264 + 265 + (string::string_regex(&pattern).unwrap()) 266 + .prop_filter_map("parseable u64", move |chunk| { 267 + chunk.repeat(n).parse::<u64>().ok() 268 + }) 269 + }) 270 + } 271 + 272 + proptest! { 273 + #[test] 274 + fn doesnt_crash(x in any::<u64>()) { 275 + repeats_multiple_times(x); 276 + } 277 + 278 + #[test] 279 + fn single_digits_cant_repeat(x in 0_u64..10) { 280 + prop_assert!(!repeats_multiple_times(x)); 281 + } 282 + 283 + #[test] 284 + fn detects_nonrepeating_numbers(x in nonrepeating()) { 285 + prop_assert!(!repeats_multiple_times(x)); 286 + } 287 + 288 + #[test] 289 + fn detects_repeating_numbers(x in repeating()) { 290 + prop_assert!(repeats_multiple_times(x)) 291 + } 292 + } 293 + } 294 + 295 + const EXAMPLE_INPUT: &str = "11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124"; 296 + 297 + mod part_1 { 298 + use super::*; 299 + 300 + const EXAMPLE_ANSWER: u64 = 1227775554; 301 + 302 + #[test] 303 + fn solves_example() { 304 + let input = parse_input(EXAMPLE_INPUT); 305 + assert_eq!(EXAMPLE_ANSWER, part_1(input)); 306 + } 307 + } 308 + 309 + mod part_2 { 310 + use super::*; 311 + 312 + const EXAMPLE_ANSWER: u64 = 4174379265; 313 + 314 + #[test] 315 + fn solves_example() { 316 + let input = parse_input(EXAMPLE_INPUT); 317 + assert_eq!(EXAMPLE_ANSWER, part_2(input)); 318 + } 319 + } 320 + }