1use utils::number::lcm;
2use utils::prelude::*;
3
4#[derive(Clone, Debug)]
12pub struct Day16 {
13 digits: Vec<u8>,
14 offset: usize,
15}
16
17const PHASES: usize = 100;
18const PART2_REPEAT: usize = 10_000;
19
20const BINOMIAL_MOD2_TERMS: [(usize, u32); 8] = [
31 (0, 5),
32 (4, 5),
33 (8, 5),
34 (12, 5),
35 (16, 5),
36 (20, 5),
37 (24, 5),
38 (28, 5),
39];
40const BINOMIAL_MOD2_PERIOD: usize = 128;
41
42const BINOMIAL_MOD5_TERMS: [(usize, u32); 2] = [(0, 6), (25, 4)];
51const BINOMIAL_MOD5_PERIOD: usize = 125;
52
53impl Day16 {
54 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
55 if let Some(index) = input.bytes().position(|b| !b.is_ascii_digit()) {
56 return Err(InputError::new(input, index, "expected digit"));
57 }
58 if input.len() < 8 {
59 return Err(InputError::new(
60 input,
61 input.len(),
62 "expected at least 8 digits",
63 ));
64 }
65
66 let digits = input.bytes().map(|b| b - b'0').collect::<Vec<_>>();
67 let offset = digits[..7]
68 .iter()
69 .fold(0usize, |acc, &digit| 10 * acc + digit as usize);
70
71 Ok(Self { digits, offset })
72 }
73
74 #[must_use]
75 pub fn part1(&self) -> u32 {
76 let mut digits = self.digits.clone();
77 let mut next = vec![0; digits.len()];
78 let mut prefix = vec![0i32; digits.len() + 1];
79
80 let len = digits.len();
81 let quarter = len / 4;
82 let third = len / 3;
83 let half = len / 2;
84
85 for _ in 0..PHASES {
86 prefix[0] = 0;
87 let mut total = 0;
88 for (&digit, prefix) in digits.iter().zip(prefix.iter_mut().skip(1)) {
89 total += i32::from(digit);
90 *prefix = total;
91 }
92
93 let block_sum = |start: usize, width: usize| {
94 prefix[(start + width).min(len)] - prefix[start.min(len)]
95 };
96
97 for (index, out) in next.iter_mut().enumerate().take(quarter) {
99 let width = index + 1;
100 let mut total = 0i32;
101 let mut start = index;
102
103 while start < len {
104 total += block_sum(start, width);
105 start += 2 * width;
106
107 if start >= len {
108 break;
109 }
110
111 total -= block_sum(start, width);
112 start += 2 * width;
113 }
114
115 *out = (total.abs() % 10) as u8;
116 }
117
118 for (index, out) in next.iter_mut().enumerate().take(third).skip(quarter) {
120 let width = index + 1;
121 let total = block_sum(index, width) - block_sum(index + 2 * width, width);
122 *out = (total.abs() % 10) as u8;
123 }
124
125 for (index, out) in next.iter_mut().enumerate().take(half).skip(third) {
127 let width = index + 1;
128 *out = (block_sum(index, width) % 10) as u8;
129 }
130
131 let mut suffix = 0u8;
133 for i in (half..len).rev() {
134 suffix += digits[i];
135 suffix = suffix.wrapping_sub(10 * u8::from(suffix >= 10));
136 next[i] = suffix;
137 }
138
139 (digits, next) = (next, digits);
140 }
141
142 digits
143 .iter()
144 .take(8)
145 .fold(0, |acc, &digit| 10 * acc + u32::from(digit))
146 }
147
148 #[must_use]
149 pub fn part2(&self) -> u32 {
150 let len = self.digits.len();
151 let total_len = len * PART2_REPEAT;
152 assert!(
153 self.offset + 8 <= total_len,
154 "expected message offset within repeated signal"
155 );
156 assert!(
157 self.offset >= total_len / 2,
158 "expected message offset in second half of repeated signal"
159 );
160
161 let mod2_skip_period = 2 * lcm(len, BINOMIAL_MOD2_PERIOD);
168 let mod5_skip_period = 5 * lcm(len, BINOMIAL_MOD5_PERIOD);
169 let mut result = 0;
170
171 for i in 0..8 {
172 let tail_len = total_len - self.offset - i;
173 let start_index = (self.offset + i) % len;
174
175 let sparse_sum = |step: usize, skip_period: usize, terms: &[(usize, u32)], mask: u8| {
176 let mut total = 0;
177 for &(residue, coefficient) in terms {
178 if residue >= tail_len {
179 break;
180 }
181
182 let remainder = tail_len - residue;
183 let skip = remainder - remainder % skip_period;
184 let mut position = residue + skip;
185 let mut index = (start_index + residue) % len;
186
187 while position < tail_len {
188 total += coefficient * u32::from(self.digits[index] & mask);
189 position += step;
190 index += step;
191 if index >= len {
192 index %= len;
193 }
194 }
195 }
196 total
197 };
198
199 let mod2_total = sparse_sum(
200 BINOMIAL_MOD2_PERIOD,
201 mod2_skip_period,
202 &BINOMIAL_MOD2_TERMS,
203 1,
204 );
205 let mod5_total = sparse_sum(
206 BINOMIAL_MOD5_PERIOD,
207 mod5_skip_period,
208 &BINOMIAL_MOD5_TERMS,
209 u8::MAX,
210 );
211 let total = mod2_total + mod5_total;
212 result = 10 * result + total % 10;
213 }
214
215 result
216 }
217}
218
219examples!(Day16 -> (u32, u32) [
220 {input: "80871224585914546619083218645595", part1: 24176176},
221 {input: "19617804207202209144916044189917", part1: 73745418},
222 {input: "69317163492948606335995924319873", part1: 52432133},
223 {input: "03036732577212944063491565474664", part2: 84462026},
224 {input: "02935109699940807407585447034323", part2: 78725270},
225 {input: "03081770884921959731165446850517", part2: 53553731},
226]);