Advent of Code for 2025!

day 1: Access the secret entrance

+192
+10
examples/day_01_secret_entrance.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 49c016dc4fd11fa9bbfb8bbf0f07e752bc534c486ad5b54b7bb24b97f083aab4 # shrinks to s = ":" 8 + cc 8492ad45bd0ba7b1c687c413c52779c63bb91de1c2ec36d4c5e58cffdeb07e9c # shrinks to s = "R2200000000" 9 + cc 99935ba16841a9db1a7e192bec5734770069262016d0e91724af515fdd5fb798 # shrinks to acc = 0, delta = 0 10 + cc 406bf951122ca7019f2f56865072c4edf7f09d75c44fab42846bcea9cbb6278a # shrinks to acc = 16, delta = -6916
+182
examples/day_01_secret_entrance.rs
··· 1 + fn main() { 2 + aoc2025::run( 3 + "Day 1: Secret Entrance", 4 + "inputs/day_01.txt", 5 + parse_input, 6 + part_1, 7 + part_2, 8 + ) 9 + .unwrap(); 10 + } 11 + 12 + /// Parses the input into a list of signed integers. 13 + fn parse_input(input: &str) -> Vec<i32> { 14 + input 15 + .split('\n') 16 + .filter(|x| !x.is_empty()) 17 + .filter_map(|x| parse_line(x)) 18 + .collect() 19 + } 20 + 21 + /// Parses the given line into a signed integer 22 + fn parse_line(line: &str) -> Option<i32> { 23 + let line = line.replace("L", "-").replace("R", ""); 24 + i32::from_str_radix(line.as_str(), 10).ok() 25 + } 26 + 27 + fn part_1(deltas: Vec<i32>) -> usize { 28 + // The input file contains a list of rotations in the form `XYY`, 29 + // where `X` is a direction (`L` for left or `R` for right) and 30 + // `YY` is a non-zero-padded number of clicks. 31 + // 32 + // The dial consists of numbers 0-99. Turning left decreases the 33 + // number, and turning right increases it. Turning too far will 34 + // overflow to the opposite end of the range. 35 + // 36 + // The dial starts at 50. Run the input file against this dial and 37 + // record the number of times it leaves it pointing at 0. 38 + 39 + // Iterate through the list and count the number of times the 40 + // accumulated sum equals zero. 41 + (deltas.into_iter()) 42 + .scan(50, |position, delta| { 43 + *position += delta; 44 + Some(*position) 45 + }) 46 + .filter(|position| position % 100 == 0) 47 + .count() 48 + } 49 + 50 + fn part_2(deltas: Vec<i32>) -> i32 { 51 + (deltas.into_iter()) 52 + .scan(50, |prev, delta| { 53 + let (next, hits) = step(*prev, delta); 54 + *prev = next; 55 + Some(hits) 56 + }) 57 + .sum() 58 + } 59 + 60 + fn step(start: i32, delta: i32) -> (i32, i32) { 61 + let mut hits = (delta / 100).abs(); 62 + 63 + let remainder = delta % 100; 64 + if start + remainder <= 0 && start != 0 || 99 < start + remainder { 65 + hits += 1; 66 + } 67 + 68 + ((start + delta).rem_euclid(100), hits) 69 + } 70 + 71 + #[cfg(test)] 72 + mod tests { 73 + use super::*; 74 + use proptest::prelude::*; 75 + 76 + prop_compose! { 77 + fn line_input()(s in -999_i32..999) -> (i32, String) { 78 + (s, format!("{}{}", if s < 0 { "L" } else { "R" }, s.abs())) 79 + } 80 + } 81 + 82 + mod parse_line { 83 + use super::*; 84 + 85 + #[test] 86 + fn ignores_gibberish() { 87 + assert_eq!(None, parse_line(":")); 88 + } 89 + 90 + proptest! { 91 + #[test] 92 + fn doesnt_crash(s in "\\PC*") { 93 + parse_line(&s); 94 + } 95 + 96 + #[test] 97 + fn parses_lines_correctly((s, line) in line_input()) { 98 + prop_assert_eq!(s, parse_line(&line).unwrap()) 99 + } 100 + } 101 + } 102 + 103 + mod parse_input { 104 + use super::*; 105 + 106 + proptest! { 107 + #[test] 108 + fn doesnt_crash(s in "\\PC+") { 109 + parse_input(&s); 110 + } 111 + 112 + #[test] 113 + fn ignores_empty_lines(s in "\\n+") { 114 + prop_assert!(parse_input(&s).is_empty()); 115 + } 116 + 117 + #[test] 118 + fn parses_files(s in prop::collection::vec(line_input(), 1..100)) { 119 + let (values, input): (Vec<i32>, Vec<String>) = s.into_iter().unzip(); 120 + prop_assert_eq!(values, parse_input(&input.join("\n"))); 121 + } 122 + } 123 + } 124 + 125 + mod part_1 { 126 + use super::*; 127 + 128 + proptest! { 129 + #[test] 130 + fn counts_halfstep_deltas(s in prop::collection::vec(Just(50_i32), 0..100)) { 131 + prop_assert_eq!((s.len() + 1) / 2, part_1(s)); 132 + } 133 + } 134 + } 135 + 136 + mod part_2 { 137 + use super::*; 138 + 139 + // Slow, naive step implementation to compare fast version against. 140 + fn step_reference(start: i32, delta: i32) -> (i32, i32) { 141 + let mut pos = start; 142 + let dir = if delta >= 0 { 1 } else { -1 }; 143 + let mut hits = 0; 144 + 145 + for _ in 0..delta.abs() { 146 + pos += dir; 147 + if pos.rem_euclid(100) == 0 { 148 + hits += 1; 149 + } 150 + } 151 + 152 + let final_acc = pos.rem_euclid(100); 153 + (final_acc, hits) 154 + } 155 + 156 + proptest! { 157 + #[test] 158 + fn counts_perfect_deltas(s in prop::collection::vec(Just(100), 0..100)) { 159 + prop_assert_eq!(s.len() as i32, part_2(s)); 160 + } 161 + 162 + #[test] 163 + fn counts_perfect_multiple_deltas(s in prop::collection::vec(0..9, 0..100)) { 164 + prop_assert_eq!( 165 + s.clone().into_iter().sum::<i32>(), 166 + part_2(s.into_iter().map(|x| x * 100).collect()) 167 + ); 168 + } 169 + 170 + #[test] 171 + fn steps_correctly( 172 + start in 0i32..100, 173 + delta in -10_000_i32..-1 174 + ) { 175 + let reference_hits = step_reference(start, delta); 176 + let real_hits = step(start, delta); 177 + 178 + prop_assert_eq!(reference_hits, real_hits); 179 + } 180 + } 181 + } 182 + }