|
| 1 | +use std::collections::VecDeque; |
| 2 | +use utils::prelude::*; |
| 3 | + |
| 4 | +/// Simulating a Turing machine. |
| 5 | +#[derive(Clone, Debug)] |
| 6 | +pub struct Day25 { |
| 7 | + start: State, |
| 8 | + steps: u32, |
| 9 | + rules: Vec<[Rule; 2]>, |
| 10 | +} |
| 11 | + |
| 12 | +#[derive(Copy, Clone, Debug)] |
| 13 | +struct Rule { |
| 14 | + write_value: bool, |
| 15 | + move_dir: Direction, |
| 16 | + next_state: State, |
| 17 | +} |
| 18 | + |
| 19 | +parser::parsable_enum! { |
| 20 | + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] |
| 21 | + enum Direction { |
| 22 | + "left" => Left = -1, |
| 23 | + "right" => Right = 1, |
| 24 | + } |
| 25 | +} |
| 26 | + |
| 27 | +parser::parsable_enum! { |
| 28 | + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] |
| 29 | + enum State { |
| 30 | + "A" => A, |
| 31 | + "B" => B, |
| 32 | + "C" => C, |
| 33 | + "D" => D, |
| 34 | + "E" => E, |
| 35 | + "F" => F, |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +impl Day25 { |
| 40 | + pub fn new(input: &str, _: InputType) -> Result<Self, InputError> { |
| 41 | + let rule = parser::byte_range(b'0'..=b'1') |
| 42 | + .map(|b| b == b'1') |
| 43 | + .with_prefix(parser::eol().then(" - Write the value ")) |
| 44 | + .with_suffix(".".then(parser::eol())) |
| 45 | + .then( |
| 46 | + Direction::PARSER |
| 47 | + .with_prefix(" - Move one slot to the ") |
| 48 | + .with_suffix(".".then(parser::eol())), |
| 49 | + ) |
| 50 | + .then( |
| 51 | + State::PARSER |
| 52 | + .with_consumed() |
| 53 | + .with_prefix(" - Continue with state ") |
| 54 | + .with_suffix(".".then(parser::eol())), |
| 55 | + ) |
| 56 | + .map(|(write_value, move_dir, (next_state, state_pos))| { |
| 57 | + ( |
| 58 | + Rule { |
| 59 | + write_value, |
| 60 | + move_dir, |
| 61 | + next_state, |
| 62 | + }, |
| 63 | + state_pos, |
| 64 | + ) |
| 65 | + }); |
| 66 | + |
| 67 | + let ((start, start_pos), steps, rules) = State::PARSER |
| 68 | + .with_consumed() |
| 69 | + .with_prefix("Begin in state ") |
| 70 | + .with_suffix(".".then(parser::eol())) |
| 71 | + .then( |
| 72 | + parser::u32() |
| 73 | + .with_prefix("Perform a diagnostic checksum after ") |
| 74 | + .with_suffix(" steps.".then(parser::eol()).then(parser::eol())), |
| 75 | + ) |
| 76 | + .then( |
| 77 | + State::PARSER |
| 78 | + .with_consumed() |
| 79 | + .with_prefix("In state ") |
| 80 | + .with_suffix(":".then(parser::eol())) |
| 81 | + .then(rule.with_prefix(" If the current value is 0:")) |
| 82 | + .then(rule.with_prefix(" If the current value is 1:")) |
| 83 | + .repeat(parser::eol(), 2), |
| 84 | + ) |
| 85 | + .parse_complete(input)?; |
| 86 | + |
| 87 | + if let Some((_, &((_, pos), _, _))) = rules |
| 88 | + .iter() |
| 89 | + .enumerate() |
| 90 | + .find(|&(i, &((s, _), _, _))| s as usize != i) |
| 91 | + { |
| 92 | + return Err(InputError::new(input, pos, "rules are not in order")); |
| 93 | + } |
| 94 | + |
| 95 | + if start as usize >= rules.len() { |
| 96 | + return Err(InputError::new(input, start_pos, "invalid start state")); |
| 97 | + } |
| 98 | + |
| 99 | + if let Some(pos) = rules |
| 100 | + .iter() |
| 101 | + .map(|(_, r0, _)| r0) |
| 102 | + .chain(rules.iter().map(|(_, _, r1)| r1)) |
| 103 | + .find_map(|&(r, pos)| (r.next_state as usize >= rules.len()).then_some(pos)) |
| 104 | + { |
| 105 | + return Err(InputError::new(input, pos, "invalid next state")); |
| 106 | + } |
| 107 | + |
| 108 | + Ok(Self { |
| 109 | + start, |
| 110 | + steps, |
| 111 | + rules: rules |
| 112 | + .into_iter() |
| 113 | + .map(|(_, (r0, _), (r1, _))| [r0, r1]) |
| 114 | + .collect(), |
| 115 | + }) |
| 116 | + } |
| 117 | + |
| 118 | + #[must_use] |
| 119 | + pub fn part1(&self) -> u32 { |
| 120 | + // Increasing the element size means a cache hit skips more steps, but also reduces the |
| 121 | + // number of cache hits. |
| 122 | + type Element = u32; |
| 123 | + |
| 124 | + let mut tape: VecDeque<Element> = VecDeque::with_capacity(512); |
| 125 | + tape.push_back(0); |
| 126 | + let mut element_index = 0; |
| 127 | + let mut bit_index = 0; |
| 128 | + let mut state = self.start; |
| 129 | + |
| 130 | + #[derive(Debug)] |
| 131 | + struct StateTransition { |
| 132 | + from_state: State, |
| 133 | + from_element: Element, |
| 134 | + to_state: State, |
| 135 | + to_element: Element, |
| 136 | + steps: u32, |
| 137 | + element_index: usize, |
| 138 | + } |
| 139 | + // Index with cache[(state as usize << 1) | (bit_index > 0) as usize] |
| 140 | + let mut cache: [Vec<StateTransition>; 12] = Default::default(); |
| 141 | + |
| 142 | + let mut step = 0; |
| 143 | + 'outer: while step < self.steps { |
| 144 | + // Ensure the element index stays within the tape |
| 145 | + if element_index == 0 { |
| 146 | + tape.push_front(0); |
| 147 | + element_index += 1; |
| 148 | + } else if element_index == tape.len() { |
| 149 | + tape.push_back(0); |
| 150 | + } |
| 151 | + |
| 152 | + // Used the cached transition if this (state, starting bit index, from element) has been |
| 153 | + // seen previously |
| 154 | + for t in &cache[(state as usize * 2) | (bit_index > 0) as usize] { |
| 155 | + if t.from_element == tape[element_index] && t.steps + step <= self.steps { |
| 156 | + while element_index > 0 |
| 157 | + && element_index < tape.len() |
| 158 | + && t.from_state == state |
| 159 | + && t.from_element == tape[element_index] |
| 160 | + && t.steps + step <= self.steps |
| 161 | + { |
| 162 | + tape[element_index] = t.to_element; |
| 163 | + element_index = element_index.wrapping_add(t.element_index); |
| 164 | + state = t.to_state; |
| 165 | + step += t.steps; |
| 166 | + } |
| 167 | + |
| 168 | + continue 'outer; |
| 169 | + } |
| 170 | + } |
| 171 | + |
| 172 | + let starting_element_index = element_index; |
| 173 | + let starting_bit_index = bit_index; |
| 174 | + let starting_element = tape[element_index]; |
| 175 | + let starting_step = step; |
| 176 | + let starting_state = state; |
| 177 | + |
| 178 | + let mut element = starting_element; |
| 179 | + while step < self.steps && bit_index < Element::BITS { |
| 180 | + let rule = &self.rules[state as usize][((element >> bit_index) & 1) as usize]; |
| 181 | + |
| 182 | + element = |
| 183 | + (element & !(1 << bit_index)) | (Element::from(rule.write_value) << bit_index); |
| 184 | + bit_index = bit_index.wrapping_add_signed(rule.move_dir as i32); |
| 185 | + state = rule.next_state; |
| 186 | + step += 1; |
| 187 | + } |
| 188 | + tape[element_index] = element; |
| 189 | + |
| 190 | + // Calculate the correct bit index and new element index |
| 191 | + if step == self.steps { |
| 192 | + break; |
| 193 | + } else if bit_index == Element::BITS { |
| 194 | + bit_index = 0; |
| 195 | + element_index += 1; |
| 196 | + } else { |
| 197 | + bit_index = Element::BITS - 1; |
| 198 | + element_index -= 1; |
| 199 | + } |
| 200 | + |
| 201 | + // Cache the transition if the machine traversed the entire element |
| 202 | + if starting_bit_index == bit_index { |
| 203 | + cache[((starting_state as usize) << 1) | (bit_index > 0) as usize].push( |
| 204 | + StateTransition { |
| 205 | + from_state: starting_state, |
| 206 | + from_element: starting_element, |
| 207 | + to_state: state, |
| 208 | + to_element: element, |
| 209 | + steps: step - starting_step, |
| 210 | + element_index: element_index.wrapping_sub(starting_element_index), |
| 211 | + }, |
| 212 | + ); |
| 213 | + } |
| 214 | + } |
| 215 | + |
| 216 | + tape.iter().map(|&v| v.count_ones()).sum() |
| 217 | + } |
| 218 | + |
| 219 | + #[must_use] |
| 220 | + pub fn part2(&self) -> &'static str { |
| 221 | + "🎄" |
| 222 | + } |
| 223 | +} |
| 224 | + |
| 225 | +examples!(Day25 -> (u32, &'static str) [ |
| 226 | + {file: "day25_example0.txt", part1: 3}, |
| 227 | +]); |
0 commit comments