···11+# Seeds for failure cases proptest has generated in the past. It is
22+# automatically read and these particular cases re-run before any
33+# novel cases are generated.
44+#
55+# It is recommended to check this file in to source control so that
66+# everyone who runs the test benefits from these saved cases.
77+cc 49c016dc4fd11fa9bbfb8bbf0f07e752bc534c486ad5b54b7bb24b97f083aab4 # shrinks to s = ":"
88+cc 8492ad45bd0ba7b1c687c413c52779c63bb91de1c2ec36d4c5e58cffdeb07e9c # shrinks to s = "R2200000000"
99+cc 99935ba16841a9db1a7e192bec5734770069262016d0e91724af515fdd5fb798 # shrinks to acc = 0, delta = 0
1010+cc 406bf951122ca7019f2f56865072c4edf7f09d75c44fab42846bcea9cbb6278a # shrinks to acc = 16, delta = -6916
+182
examples/day_01_secret_entrance.rs
···11+fn main() {
22+ aoc2025::run(
33+ "Day 1: Secret Entrance",
44+ "inputs/day_01.txt",
55+ parse_input,
66+ part_1,
77+ part_2,
88+ )
99+ .unwrap();
1010+}
1111+1212+/// Parses the input into a list of signed integers.
1313+fn parse_input(input: &str) -> Vec<i32> {
1414+ input
1515+ .split('\n')
1616+ .filter(|x| !x.is_empty())
1717+ .filter_map(|x| parse_line(x))
1818+ .collect()
1919+}
2020+2121+/// Parses the given line into a signed integer
2222+fn parse_line(line: &str) -> Option<i32> {
2323+ let line = line.replace("L", "-").replace("R", "");
2424+ i32::from_str_radix(line.as_str(), 10).ok()
2525+}
2626+2727+fn part_1(deltas: Vec<i32>) -> usize {
2828+ // The input file contains a list of rotations in the form `XYY`,
2929+ // where `X` is a direction (`L` for left or `R` for right) and
3030+ // `YY` is a non-zero-padded number of clicks.
3131+ //
3232+ // The dial consists of numbers 0-99. Turning left decreases the
3333+ // number, and turning right increases it. Turning too far will
3434+ // overflow to the opposite end of the range.
3535+ //
3636+ // The dial starts at 50. Run the input file against this dial and
3737+ // record the number of times it leaves it pointing at 0.
3838+3939+ // Iterate through the list and count the number of times the
4040+ // accumulated sum equals zero.
4141+ (deltas.into_iter())
4242+ .scan(50, |position, delta| {
4343+ *position += delta;
4444+ Some(*position)
4545+ })
4646+ .filter(|position| position % 100 == 0)
4747+ .count()
4848+}
4949+5050+fn part_2(deltas: Vec<i32>) -> i32 {
5151+ (deltas.into_iter())
5252+ .scan(50, |prev, delta| {
5353+ let (next, hits) = step(*prev, delta);
5454+ *prev = next;
5555+ Some(hits)
5656+ })
5757+ .sum()
5858+}
5959+6060+fn step(start: i32, delta: i32) -> (i32, i32) {
6161+ let mut hits = (delta / 100).abs();
6262+6363+ let remainder = delta % 100;
6464+ if start + remainder <= 0 && start != 0 || 99 < start + remainder {
6565+ hits += 1;
6666+ }
6767+6868+ ((start + delta).rem_euclid(100), hits)
6969+}
7070+7171+#[cfg(test)]
7272+mod tests {
7373+ use super::*;
7474+ use proptest::prelude::*;
7575+7676+ prop_compose! {
7777+ fn line_input()(s in -999_i32..999) -> (i32, String) {
7878+ (s, format!("{}{}", if s < 0 { "L" } else { "R" }, s.abs()))
7979+ }
8080+ }
8181+8282+ mod parse_line {
8383+ use super::*;
8484+8585+ #[test]
8686+ fn ignores_gibberish() {
8787+ assert_eq!(None, parse_line(":"));
8888+ }
8989+9090+ proptest! {
9191+ #[test]
9292+ fn doesnt_crash(s in "\\PC*") {
9393+ parse_line(&s);
9494+ }
9595+9696+ #[test]
9797+ fn parses_lines_correctly((s, line) in line_input()) {
9898+ prop_assert_eq!(s, parse_line(&line).unwrap())
9999+ }
100100+ }
101101+ }
102102+103103+ mod parse_input {
104104+ use super::*;
105105+106106+ proptest! {
107107+ #[test]
108108+ fn doesnt_crash(s in "\\PC+") {
109109+ parse_input(&s);
110110+ }
111111+112112+ #[test]
113113+ fn ignores_empty_lines(s in "\\n+") {
114114+ prop_assert!(parse_input(&s).is_empty());
115115+ }
116116+117117+ #[test]
118118+ fn parses_files(s in prop::collection::vec(line_input(), 1..100)) {
119119+ let (values, input): (Vec<i32>, Vec<String>) = s.into_iter().unzip();
120120+ prop_assert_eq!(values, parse_input(&input.join("\n")));
121121+ }
122122+ }
123123+ }
124124+125125+ mod part_1 {
126126+ use super::*;
127127+128128+ proptest! {
129129+ #[test]
130130+ fn counts_halfstep_deltas(s in prop::collection::vec(Just(50_i32), 0..100)) {
131131+ prop_assert_eq!((s.len() + 1) / 2, part_1(s));
132132+ }
133133+ }
134134+ }
135135+136136+ mod part_2 {
137137+ use super::*;
138138+139139+ // Slow, naive step implementation to compare fast version against.
140140+ fn step_reference(start: i32, delta: i32) -> (i32, i32) {
141141+ let mut pos = start;
142142+ let dir = if delta >= 0 { 1 } else { -1 };
143143+ let mut hits = 0;
144144+145145+ for _ in 0..delta.abs() {
146146+ pos += dir;
147147+ if pos.rem_euclid(100) == 0 {
148148+ hits += 1;
149149+ }
150150+ }
151151+152152+ let final_acc = pos.rem_euclid(100);
153153+ (final_acc, hits)
154154+ }
155155+156156+ proptest! {
157157+ #[test]
158158+ fn counts_perfect_deltas(s in prop::collection::vec(Just(100), 0..100)) {
159159+ prop_assert_eq!(s.len() as i32, part_2(s));
160160+ }
161161+162162+ #[test]
163163+ fn counts_perfect_multiple_deltas(s in prop::collection::vec(0..9, 0..100)) {
164164+ prop_assert_eq!(
165165+ s.clone().into_iter().sum::<i32>(),
166166+ part_2(s.into_iter().map(|x| x * 100).collect())
167167+ );
168168+ }
169169+170170+ #[test]
171171+ fn steps_correctly(
172172+ start in 0i32..100,
173173+ delta in -10_000_i32..-1
174174+ ) {
175175+ let reference_hits = step_reference(start, delta);
176176+ let real_hits = step(start, delta);
177177+178178+ prop_assert_eq!(reference_hits, real_hits);
179179+ }
180180+ }
181181+ }
182182+}