···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 162969ac4b49f148171bb87379422306eeff964439b9413525aac38d4d347679 # shrinks to s = "𞹡"
88+cc 49579348452c91933743f79fb03bd9688e9c20fde27f00ae2e10d9b1e1c115cf # shrinks to x = 1
+320
examples/day_02_gift_shop.rs
···11+use std::ops::RangeInclusive;
22+33+fn main() {
44+ aoc2025::run(
55+ "Day 2: Gift Shop",
66+ "inputs/day_02.txt",
77+ parse_input,
88+ part_1,
99+ part_2,
1010+ )
1111+ .unwrap();
1212+}
1313+1414+fn parse_chunk(chunk: &str) -> Option<RangeInclusive<u64>> {
1515+ let chunk = chunk.trim();
1616+ let (start, end) = chunk.find('-').map(|i| chunk.split_at(i))?;
1717+ let start = start.parse::<u64>().ok();
1818+ let end = end.trim_start_matches('-').parse::<u64>().ok();
1919+ start.zip(end).map(|(start, end)| start..=end)
2020+}
2121+2222+fn parse_input(input: &str) -> Vec<RangeInclusive<u64>> {
2323+ (input.split(','))
2424+ .filter_map(|ch| parse_chunk(ch))
2525+ .collect::<Vec<_>>()
2626+}
2727+2828+fn repeats_once(value: u64) -> bool {
2929+ let value = value.to_string();
3030+3131+ if value.len() % 2 != 0 {
3232+ return false;
3333+ }
3434+3535+ let (start, end) = value.split_at(value.len() / 2);
3636+ start == end
3737+}
3838+3939+fn factors(x: usize) -> Vec<usize> {
4040+ (1..=x)
4141+ .filter_map(|i| if x % i == 0 { Some(i) } else { None })
4242+ .collect::<Vec<_>>()
4343+}
4444+4545+fn repeats_multiple_times(value: u64) -> bool {
4646+ let value = value.to_string();
4747+ let value = value.as_bytes();
4848+4949+ if value.len() <= 1 {
5050+ return false;
5151+ }
5252+5353+ for l in factors(value.len()) {
5454+ if l == value.len() {
5555+ continue;
5656+ }
5757+5858+ let first = &value[0..l];
5959+ if value.chunks(l).all(|c| c == first) {
6060+ return true;
6161+ }
6262+ }
6363+6464+ false
6565+}
6666+6767+fn part_1(ranges: Vec<RangeInclusive<u64>>) -> u64 {
6868+ (ranges.into_iter())
6969+ .map(|range| range.filter(|x| repeats_once(*x)).sum::<u64>())
7070+ .sum()
7171+}
7272+7373+fn part_2(ranges: Vec<RangeInclusive<u64>>) -> u64 {
7474+ (ranges.into_iter())
7575+ .map(|range| range.filter(|x| repeats_multiple_times(*x)).sum::<u64>())
7676+ .sum()
7777+}
7878+7979+#[cfg(test)]
8080+mod tests {
8181+ use std::ops::RangeInclusive;
8282+8383+ use super::*;
8484+ use proptest::prelude::*;
8585+8686+ prop_compose! {
8787+ fn chunk()(
8888+ x in any::<u64>(),
8989+ y in any::<u64>(),
9090+ w0 in "\\s*",
9191+ w1 in "\\s*"
9292+ ) -> (RangeInclusive<u64>, String) {
9393+ let start = x.min(y);
9494+ let end = x.max(y);
9595+9696+ (start..=end, format!("{}{}-{}{}", w0, start, end, w1))
9797+ }
9898+ }
9999+100100+ prop_compose! {
101101+ fn input()(v in prop::collection::vec(chunk(), 1..100)) -> (Vec<RangeInclusive<u64>>, String) {
102102+ let (ranges, chunks): (Vec<_>, Vec<_>) = v.into_iter().unzip();
103103+ (ranges, chunks.join(","))
104104+ }
105105+ }
106106+107107+ mod parse_chunk {
108108+ use super::*;
109109+110110+ proptest! {
111111+ #[test]
112112+ fn doesnt_crash(s in "\\PC+") {
113113+ parse_chunk(s.as_str());
114114+ }
115115+116116+ #[test]
117117+ fn parses_chunks(ch in chunk()) {
118118+ let (expected, input) = ch;
119119+ prop_assert_eq!(Some(expected), parse_chunk(&input));
120120+ }
121121+ }
122122+ }
123123+124124+ mod parse_input {
125125+ use super::*;
126126+127127+ proptest! {
128128+ #[test]
129129+ fn doesnt_crash(s in "\\PC+") {
130130+ parse_input(s.as_str());
131131+ }
132132+133133+ #[test]
134134+ fn ignores_empty_chunks(s in ",+") {
135135+ prop_assert_eq!(Vec::<RangeInclusive<u64>>::new(), parse_input(&s));
136136+ }
137137+138138+ #[test]
139139+ fn parses_input_correctly(s in input()) {
140140+ let (ranges, input) = s;
141141+ prop_assert_eq!(ranges, parse_input(&input));
142142+ }
143143+ }
144144+ }
145145+146146+ mod repeats_once {
147147+ use super::*;
148148+149149+ fn odd_length() -> impl Strategy<Value = u64> {
150150+ any::<u64>().prop_filter("must be odd length", |x| x.to_string().len() % 2 != 0)
151151+ }
152152+153153+ fn even_length_nonrepeating() -> impl Strategy<Value = u64> {
154154+ any::<u64>()
155155+ .prop_filter("must be even length", |x| x.to_string().len() % 2 == 0)
156156+ .prop_filter("must be nonrepeating", |x| {
157157+ let x = x.to_string();
158158+ let (a, b) = x.split_at(x.len() / 2);
159159+ a != b
160160+ })
161161+ }
162162+163163+ const MAX_SUBNUMBER: u64 = 1_844_674_407;
164164+165165+ prop_compose! {
166166+ fn even_length_repeating()(x in 0_u64..MAX_SUBNUMBER) -> u64 {
167167+ format!("{}{}", x, x).parse::<u64>().unwrap()
168168+ }
169169+ }
170170+171171+ proptest! {
172172+ #[test]
173173+ fn doesnt_crash(x in any::<u64>()) {
174174+ repeats_once(x);
175175+ }
176176+177177+ #[test]
178178+ fn odd_lengths_cant_repeat(x in odd_length()) {
179179+ prop_assert!(!repeats_once(x));
180180+ }
181181+182182+ #[test]
183183+ fn detects_nonrepeating(x in even_length_nonrepeating()) {
184184+ prop_assert!(!repeats_once(x));
185185+ }
186186+187187+ #[test]
188188+ fn detects_repeating_once(x in even_length_repeating()) {
189189+ prop_assert!(repeats_once(x));
190190+ }
191191+ }
192192+ }
193193+194194+ mod factors {
195195+ use super::*;
196196+197197+ // We can test smaller ranges here because `factors` will only be used
198198+ // on the length of the string representation of a number, not the
199199+ // number itself.
200200+201201+ proptest! {
202202+ #[test]
203203+ fn doesnt_crash(x in 1_usize..=20) {
204204+ factors(x);
205205+ }
206206+207207+ #[test]
208208+ fn always_includes_one(x in 1_usize..=20) {
209209+ prop_assert!(factors(x).contains(&1))
210210+ }
211211+212212+ #[test]
213213+ fn always_includes_input(x in 1_usize..=20) {
214214+ prop_assert!(factors(x).contains(&x))
215215+ }
216216+217217+ #[test]
218218+ fn factors_correctly(x in 1_usize..=10, y in 1_usize..=10) {
219219+ let f = factors(x * y);
220220+ prop_assert!(f.contains(&x));
221221+ prop_assert!(f.contains(&y));
222222+ }
223223+ }
224224+ }
225225+226226+ mod repeats_multiple_times {
227227+ use proptest::string;
228228+229229+ use super::*;
230230+231231+ // Any string that repeats X times must be a multiple of that length
232232+ // Therefore, find the factors of len(s) and get the substring chunks
233233+ // If all are equal for any factor, return true
234234+235235+ const PRIME_LENGTHS: &[usize] = &[2, 3, 5, 7, 11, 13, 17, 19];
236236+237237+ // Doesn't cover all nonrepeating numbers but that's a lot of work and
238238+ // this should be good enough
239239+ fn nonrepeating() -> impl Strategy<Value = u64> {
240240+ prop::sample::select(PRIME_LENGTHS.to_vec()).prop_flat_map(|len| {
241241+ let pattern = format!("[1-9][0-9]{{{}}}", len - 1);
242242+ string::string_regex(pattern.as_str())
243243+ .unwrap()
244244+ .prop_filter_map("not all digits identical", move |s| {
245245+ let mut chars = s.chars();
246246+ let first = chars.next().unwrap();
247247+ if chars.all(|c| c == first) {
248248+ return None;
249249+ }
250250+251251+ s.parse::<u64>().ok()
252252+ })
253253+ })
254254+ }
255255+256256+ fn repeating() -> impl Strategy<Value = u64> {
257257+ (2_usize..10, 1_usize..10)
258258+ .prop_filter("total length must fit within u64", |(x, y)| x * y < 20)
259259+ .prop_flat_map(|(n, length)| {
260260+ let pattern = match length {
261261+ 1 => "[1-9]".to_string(),
262262+ l => format!("[1-9][0-9]{{{}}}", l - 1),
263263+ };
264264+265265+ (string::string_regex(&pattern).unwrap())
266266+ .prop_filter_map("parseable u64", move |chunk| {
267267+ chunk.repeat(n).parse::<u64>().ok()
268268+ })
269269+ })
270270+ }
271271+272272+ proptest! {
273273+ #[test]
274274+ fn doesnt_crash(x in any::<u64>()) {
275275+ repeats_multiple_times(x);
276276+ }
277277+278278+ #[test]
279279+ fn single_digits_cant_repeat(x in 0_u64..10) {
280280+ prop_assert!(!repeats_multiple_times(x));
281281+ }
282282+283283+ #[test]
284284+ fn detects_nonrepeating_numbers(x in nonrepeating()) {
285285+ prop_assert!(!repeats_multiple_times(x));
286286+ }
287287+288288+ #[test]
289289+ fn detects_repeating_numbers(x in repeating()) {
290290+ prop_assert!(repeats_multiple_times(x))
291291+ }
292292+ }
293293+ }
294294+295295+ 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";
296296+297297+ mod part_1 {
298298+ use super::*;
299299+300300+ const EXAMPLE_ANSWER: u64 = 1227775554;
301301+302302+ #[test]
303303+ fn solves_example() {
304304+ let input = parse_input(EXAMPLE_INPUT);
305305+ assert_eq!(EXAMPLE_ANSWER, part_1(input));
306306+ }
307307+ }
308308+309309+ mod part_2 {
310310+ use super::*;
311311+312312+ const EXAMPLE_ANSWER: u64 = 4174379265;
313313+314314+ #[test]
315315+ fn solves_example() {
316316+ let input = parse_input(EXAMPLE_INPUT);
317317+ assert_eq!(EXAMPLE_ANSWER, part_2(input));
318318+ }
319319+ }
320320+}