year2017/
day25.rs

1use std::collections::VecDeque;
2use utils::prelude::*;
3
4/// Simulating a Turing machine.
5#[derive(Clone, Debug)]
6pub struct Day25 {
7    start: State,
8    steps: u32,
9    rules: Vec<[Rule; 2]>,
10}
11
12#[derive(Copy, Clone, Debug)]
13struct Rule {
14    write_value: bool,
15    move_dir: Direction,
16    next_state: State,
17}
18
19parser::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
27parser::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
39impl 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
225examples!(Day25 -> (u32, &'static str) [
226    {file: "day25_example0.txt", part1: 3},
227]);