1use utils::prelude::*;
2
3#[derive(Clone, Debug)]
13pub struct Day14 {
14 part1: [u8; 10],
15 part2: usize,
16}
17
18impl Day14 {
19 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
20 let pattern = parser::digit()
21 .repeat_n(parser::noop())
22 .parse_complete(input)?;
23 let index = input.parse().unwrap();
24
25 let mut searcher = Searcher::new(pattern);
26 let part2 = searcher.run();
27
28 let mut part1 = [0u8; 10];
30 part1.copy_from_slice(&searcher.recipes[index..index + 10]);
31 for x in &mut part1 {
32 *x += b'0';
33 }
34
35 Ok(Self { part1, part2 })
36 }
37
38 #[must_use]
39 pub fn part1(&self) -> &str {
40 str::from_utf8(&self.part1).unwrap()
41 }
42
43 #[must_use]
44 pub fn part2(&self) -> usize {
45 self.part2
46 }
47}
48
49struct Searcher {
50 pattern: [u8; 6],
51 recipes: Vec<u8>,
52 recipes_len: usize,
53 packed: Vec<u8>,
54 packed_len: usize,
55 packed_next_idx: usize,
56 packed_last_idx: usize,
57 elves: [usize; 2],
58 initial: [i32; 2],
59 searched: usize,
60}
61
62const INITIAL_SEQUENCES: [u32; 9] = [
64 0xfff9_4113,
65 0xffff_0657,
66 0xfff9_4111,
67 0xfff9_4110,
68 0xffff_9411,
69 0xffff_9410,
70 0xffff_f941,
71 0xffff_1812,
72 0xffff_ff94,
73];
74
75const RECIPES: [u8; 23] = [
77 3, 7, 1, 0, 1, 0, 1, 2, 4, 5, 1, 5, 8, 9, 1, 6, 7, 7, 9, 2, 5, 1, 0,
78];
79
80const RECIPES_LEN: usize = 22_000_000;
81const PACKED_LEN: usize = RECIPES_LEN / 4;
82const SEARCH_INTERVAL: usize = 16384;
83
84impl Searcher {
85 fn new(pattern: [u8; 6]) -> Self {
86 let mut recipes = vec![1u8; RECIPES_LEN];
89 recipes[..RECIPES.len()].copy_from_slice(&RECIPES);
90
91 Self {
92 pattern,
93 recipes,
94 recipes_len: RECIPES.len(),
95 packed: vec![0u8; PACKED_LEN],
96 packed_len: 0,
97 packed_next_idx: RECIPES.len(),
98 packed_last_idx: RECIPES.len(),
99 elves: [0, 0],
100 initial: [INITIAL_SEQUENCES[0] as i32, INITIAL_SEQUENCES[8] as i32],
101 searched: 0,
102 }
103 }
104
105 #[inline]
106 fn append_recipe(&mut self, mut recipe: u8) {
107 if recipe >= 10 {
109 recipe -= 10;
110 if self.recipes_len == self.packed_next_idx {
111 self.packed[self.packed_len] = 1;
112 self.packed_len += 1;
113 self.packed_last_idx = self.packed_next_idx;
114 self.packed_next_idx += 2;
115 }
116 self.recipes_len += 1;
118 }
119
120 if self.recipes_len == self.packed_next_idx {
122 self.packed[self.packed_len] = recipe;
123 self.packed_len += 1;
124 self.packed_last_idx = self.packed_next_idx;
125 self.packed_next_idx += recipe as usize + 1;
126 }
127 self.recipes[self.recipes_len] = recipe;
128 self.recipes_len += 1;
129
130 for i in 0..2 {
132 if self.elves[i] == self.packed_len {
133 let initial_index = self.packed_last_idx
134 + (self.packed[self.packed_len - 1] as usize + 1)
135 - self.recipes_len;
136 self.initial[i] = INITIAL_SEQUENCES[initial_index] as i32;
137 self.elves[i] = 0;
138 }
139 }
140 }
141
142 #[inline]
143 fn run(&mut self) -> usize {
144 loop {
145 while self.initial[0] != -1 || self.initial[1] != -1 {
147 let mut recipe = 0;
148 for i in 0..2 {
149 if self.initial[i] == -1 {
150 recipe += self.packed[self.elves[i]];
151 self.elves[i] += 1;
152 } else {
153 recipe += (self.initial[i] & 0xF) as u8;
154 self.initial[i] >>= 4;
155 }
156 }
157 self.append_recipe(recipe);
158 }
159
160 loop {
162 let mut iterations = (self.packed_len - self.elves[0].max(self.elves[1])) / 32;
163 if iterations == 0 {
164 break;
165 }
166
167 while iterations > 0 {
168 if iterations > 16 {
169 self.bulk_mix::<512>();
170 iterations -= 16;
171 } else {
172 self.bulk_mix::<32>();
173 iterations -= 1;
174 }
175
176 if self.searched + SEARCH_INTERVAL < self.recipes_len
178 && let Some(index) = self.search()
179 {
180 return index;
181 }
182 }
183
184 while self.packed_next_idx < self.recipes_len {
186 self.packed_last_idx = self.packed_next_idx;
187 self.packed[self.packed_len] = self.recipes[self.packed_next_idx];
188 self.packed_len += 1;
189 self.packed_next_idx += self.recipes[self.packed_next_idx] as usize + 1;
190 }
191 }
192
193 while self.initial[0] == -1 && self.initial[1] == -1 {
195 let sum = self.packed[self.elves[0]] + self.packed[self.elves[1]];
196 self.elves[0] += 1;
197 self.elves[1] += 1;
198 self.append_recipe(sum);
199 }
200 }
201 }
202
203 #[inline]
204 fn bulk_mix<const N: usize>(&mut self) {
205 const { assert!(N.is_multiple_of(32)) }
206
207 let v0 = &self.packed[self.elves[0]..self.elves[0] + N];
208 let v1 = &self.packed[self.elves[1]..self.elves[1] + N];
209 self.elves[0] += N;
210 self.elves[1] += N;
211
212 let mut digits = [0u8; N];
215 let mut carry = [0u8; N];
216 for i in 0..N {
217 let s = v0[i] + v1[i];
218 let c = u8::from(s >= 10);
219 carry[i] = c;
220 digits[i] = s - (c * 10);
221 }
222
223 let (digits, []) = digits.as_chunks::<8>() else {
227 unreachable!("N is a multiple of 8");
228 };
229 let (carry, []) = carry.as_chunks::<8>() else {
230 unreachable!("N is a multiple of 8");
231 };
232 for (digits, carry) in digits.iter().zip(carry) {
233 let slice = &mut self.recipes[self.recipes_len..self.recipes_len + 256];
234
235 let mut indexes = u64::from_le_bytes(*carry);
236 indexes += 0x0101_0101_0101_0100;
238 indexes += indexes << 8;
240 indexes += indexes << 16;
241 indexes += indexes << 32;
242
243 for (i, &d) in digits.iter().enumerate() {
244 let idx = (indexes >> (i * 8)) as u8;
247 slice[idx as usize] = d;
248 }
249
250 self.recipes_len += (indexes >> 56) as usize + 1;
251 }
252 }
253
254 #[inline]
255 fn search(&mut self) -> Option<usize> {
256 while self.searched + 64 + 5 < self.recipes_len {
258 let mut candidates = [0u8; 64];
261 let mut any_matches = false;
262 for (i, (&a, &b)) in self.recipes[self.searched..self.searched + 64]
263 .iter()
264 .zip(&self.recipes[self.searched + 1..self.searched + 65])
265 .enumerate()
266 {
267 let matches = (a == self.pattern[0]) & (b == self.pattern[1]);
268 candidates[i] = u8::from(matches).wrapping_neg();
271 any_matches |= matches;
272 }
273
274 if !any_matches {
277 self.searched += 64;
278 continue;
279 }
280
281 let mut mask = candidates
282 .iter()
283 .enumerate()
284 .fold(0u64, |acc, (i, &x)| acc | u64::from(x != 0) << i);
285
286 while mask != 0 {
288 let m = mask.trailing_zeros() as usize;
289 mask &= !(1 << m);
290
291 let index = self.searched + m;
292 if self.recipes[index..index + 6] == self.pattern {
293 return Some(index);
294 }
295 }
296
297 self.searched += 64;
298 }
299
300 None
301 }
302}
303
304examples!(Day14 -> (&'static str, usize) [
305 {input: "100100", part1: "8239938101", part2: 377203},
307 {input: "123456", part1: "1371276618", part2: 450697},
308 {input: "924933", part1: "0012267210", part2: 16462928},
309 {input: "054274", part1: "6112136872", part2: 21000203},
310]);