Advent of Code for 2025!
1fn main() {
2 aoc2025::run(
3 "Day 3: Lobby",
4 "inputs/day_03.txt",
5 parse_input,
6 part_1,
7 part_2,
8 )
9 .unwrap();
10}
11
12fn parse_bank(bank: &str) -> Vec<u64> {
13 bank.chars()
14 .filter_map(|ch| ch.to_digit(10))
15 .map(|x| x as u64)
16 .filter(|x| *x != 0)
17 .collect::<Vec<_>>()
18}
19
20fn parse_input(input: &str) -> Vec<Vec<u64>> {
21 (input.split("\n"))
22 .map(|s| parse_bank(s))
23 .filter(|v| !v.is_empty())
24 .collect::<Vec<_>>()
25}
26
27fn find_highest_naive_joltage(bank: Vec<u64>) -> u64 {
28 let mut max = 0;
29 for (i, a) in bank.iter().enumerate() {
30 for b in &bank[(i + 1)..bank.len()] {
31 max = max.max(a * 10 + b);
32 }
33 }
34 max
35}
36
37fn find_highest_joltage(bank: Vec<u64>) -> u64 {
38 let n = bank.len();
39
40 let keep = 12_usize;
41 let mut result: Vec<u64> = Vec::with_capacity(keep);
42
43 let mut start = 0_usize;
44 let mut remaining = keep;
45
46 while remaining > 0 {
47 let end = n - remaining;
48
49 let mut best_digit = 0_u64;
50 let mut best_index = start;
51
52 for i in start..=end {
53 let d = bank[i];
54 if d > best_digit {
55 best_digit = d;
56 best_index = i;
57
58 if d == 9 {
59 break;
60 }
61 }
62 }
63
64 result.push(best_digit);
65 start = best_index + 1;
66 remaining -= 1;
67 }
68
69 let s: String = result.iter().map(|d| d.to_string()).collect();
70 s.parse::<u64>().unwrap()
71}
72
73fn part_1(banks: Vec<Vec<u64>>) -> u64 {
74 (banks.iter())
75 .map(|bank| find_highest_naive_joltage(bank.clone()))
76 .sum()
77}
78
79fn part_2(banks: Vec<Vec<u64>>) -> u64 {
80 (banks.iter())
81 .map(|bank| find_highest_joltage(bank.clone()))
82 .sum()
83}
84
85#[cfg(test)]
86mod test {
87 use super::*;
88 use proptest::prelude::*;
89
90 fn bank() -> impl Strategy<Value = Vec<u64>> {
91 prop::collection::vec(1_u64..10, 2..200)
92 }
93
94 fn rigged_bank(rig_count: usize) -> impl Strategy<Value = (Vec<u64>, Vec<u64>)> {
95 let min_len = rig_count.max(2);
96
97 prop::collection::vec(1_u64..5, min_len..20).prop_flat_map(move |bank| {
98 let len = bank.len();
99 let all_indices: Vec<usize> = (0..len).collect();
100
101 let indices_strategy = prop::sample::subsequence(all_indices.clone(), rig_count);
102
103 let values_strategy =
104 prop::collection::vec(6_u64..10, rig_count).prop_map(|mut vals| {
105 vals.sort_by(|a, b| b.cmp(a));
106 vals
107 });
108
109 (indices_strategy, values_strategy).prop_map(move |(indices, rigged_values)| {
110 let mut bank = bank.clone();
111
112 for (idx, val) in indices.iter().zip(rigged_values.iter()) {
113 bank[*idx] = *val;
114 }
115
116 (bank, rigged_values)
117 })
118 })
119 }
120
121 prop_compose! {
122 fn bank_input()(bank in bank()) -> (Vec<u64>, String) {
123 let input = bank.iter().map(|x| x.to_string()).collect::<Vec<_>>().join("");
124 (bank, input)
125 }
126 }
127
128 prop_compose! {
129 fn input()(banks in prop::collection::vec(bank_input(), 1..200)) -> (Vec<Vec<u64>>, String) {
130 let (values, input): (Vec<_>, Vec<_>) = banks.into_iter().unzip();
131 (values, input.join("\n"))
132 }
133 }
134
135 mod parse_bank {
136 use super::*;
137
138 proptest! {
139 #[test]
140 fn doesnt_crash(s in "\\PC+") {
141 parse_bank(s.as_str());
142 }
143
144 #[test]
145 fn doesnt_parse_nondigits(s in "[^[1-9]]+") {
146 prop_assert!(parse_bank(s.as_str()).is_empty());
147 }
148
149 #[test]
150 fn correctly_parses_digits((values, input) in bank_input()) {
151 prop_assert_eq!(values, parse_bank(&input));
152 }
153 }
154 }
155
156 mod parse_input {
157 use super::*;
158
159 proptest! {
160 #[test]
161 fn doesnt_crash(s in "\\PC+") {
162 parse_input(s.as_str());
163 }
164
165 #[test]
166 fn ignores_empty_banks(s in "\\n+") {
167 prop_assert!(parse_input(s.as_str()).is_empty());
168 }
169
170 #[test]
171 fn correctly_parses_banks((values, input) in input()) {
172 prop_assert_eq!(values, parse_input(input.as_str()));
173 }
174 }
175 }
176
177 mod find_highest_naive_joltage {
178 use super::*;
179
180 proptest! {
181 #[test]
182 fn handles_two_joltages(x in 1_u64..10, y in 1_u64..10) {
183 prop_assert_eq!(x * 10 + y, find_highest_naive_joltage(vec![x, y]));
184 }
185
186 #[test]
187 fn maximizes_bank_joltage((bank, values) in rigged_bank(2)) {
188 let values = values.iter().map(|x| x.to_string()).collect::<Vec<_>>().join("").parse::<u64>().unwrap();
189 prop_assert_eq!(values, find_highest_naive_joltage(bank));
190 }
191 }
192 }
193
194 mod find_highest_joltage {
195 use super::*;
196
197 proptest! {
198 #[test]
199 fn maximizes_bank_joltage((bank, values) in rigged_bank(12)) {
200 let values = values.iter().map(|x| x.to_string()).collect::<Vec<_>>().join("").parse::<u64>().unwrap();
201 prop_assert_eq!(values, find_highest_joltage(bank));
202 }
203 }
204 }
205
206 const EXAMPLE_INPUT: &str = r#"
207 987654321111111
208 811111111111119
209 234234234234278
210 818181911112111
211 "#;
212
213 mod part_1 {
214 use super::*;
215
216 const EXAMPLE_ANSWER: u64 = 357;
217
218 #[test]
219 fn solves_example() {
220 let input = parse_input(EXAMPLE_INPUT);
221 assert_eq!(EXAMPLE_ANSWER, part_1(input));
222 }
223 }
224
225 mod part_2 {
226 use super::*;
227
228 const EXAMPLE_ANSWER: u64 = 3_121_910_778_619;
229
230 #[test]
231 fn solves_example() {
232 let input = parse_input(EXAMPLE_INPUT);
233 assert_eq!(EXAMPLE_ANSWER, part_2(input));
234 }
235 }
236}