Advent of Code for 2025!
1fn 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.
13fn 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
22fn 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
27fn 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
50fn 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
60fn 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)]
72mod 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}