Skip to main content

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(".".with_eol())
45            .then(
46                Direction::PARSER
47                    .with_prefix("    - Move one slot to the ")
48                    .with_suffix(".".with_eol()),
49            )
50            .then(
51                State::PARSER
52                    .with_consumed()
53                    .with_prefix("    - Continue with state ")
54                    .with_suffix(".".with_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(".".with_eol())
71            .then(
72                parser::u32()
73                    .with_prefix("Perform a diagnostic checksum after ")
74                    .with_suffix(" steps.".with_eol().with_eol()),
75            )
76            .then(
77                State::PARSER
78                    .with_consumed()
79                    .with_prefix("In state ")
80                    .with_suffix(":".with_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        #[derive(Debug)]
125        struct StateTransition {
126            from_state: State,
127            from_element: Element,
128            to_state: State,
129            to_element: Element,
130            steps: u32,
131            element_index: usize,
132        }
133
134        let mut tape: VecDeque<Element> = VecDeque::with_capacity(512);
135        tape.push_back(0);
136        let mut element_index = 0;
137        let mut bit_index = 0;
138        let mut state = self.start;
139
140        // Index with cache[(state as usize << 1) | (bit_index > 0) as usize]
141        let mut cache: [Vec<StateTransition>; 12] = Default::default();
142
143        let mut step = 0;
144        'outer: while step < self.steps {
145            // Ensure the element index stays within the tape
146            if element_index == 0 {
147                tape.push_front(0);
148                element_index += 1;
149            } else if element_index == tape.len() {
150                tape.push_back(0);
151            }
152
153            // Used the cached transition if this (state, starting bit index, from element) has been
154            // seen previously
155            for t in &cache[(state as usize * 2) | usize::from(bit_index > 0)] {
156                if t.from_element == tape[element_index] && t.steps + step <= self.steps {
157                    while element_index > 0
158                        && element_index < tape.len()
159                        && t.from_state == state
160                        && t.from_element == tape[element_index]
161                        && t.steps + step <= self.steps
162                    {
163                        tape[element_index] = t.to_element;
164                        element_index = element_index.wrapping_add(t.element_index);
165                        state = t.to_state;
166                        step += t.steps;
167                    }
168
169                    continue 'outer;
170                }
171            }
172
173            let starting_element_index = element_index;
174            let starting_bit_index = bit_index;
175            let starting_element = tape[element_index];
176            let starting_step = step;
177            let starting_state = state;
178
179            let mut element = starting_element;
180            while step < self.steps && bit_index < Element::BITS {
181                let rule = &self.rules[state as usize][((element >> bit_index) & 1) as usize];
182
183                element =
184                    (element & !(1 << bit_index)) | (Element::from(rule.write_value) << bit_index);
185                bit_index = bit_index.wrapping_add_signed(rule.move_dir as i32);
186                state = rule.next_state;
187                step += 1;
188            }
189            tape[element_index] = element;
190
191            // Calculate the correct bit index and new element index
192            if step == self.steps {
193                break;
194            } else if bit_index == Element::BITS {
195                bit_index = 0;
196                element_index += 1;
197            } else {
198                bit_index = Element::BITS - 1;
199                element_index -= 1;
200            }
201
202            // Cache the transition if the machine traversed the entire element
203            if starting_bit_index == bit_index {
204                cache[((starting_state as usize) << 1) | usize::from(bit_index > 0)].push(
205                    StateTransition {
206                        from_state: starting_state,
207                        from_element: starting_element,
208                        to_state: state,
209                        to_element: element,
210                        steps: step - starting_step,
211                        element_index: element_index.wrapping_sub(starting_element_index),
212                    },
213                );
214            }
215        }
216
217        tape.iter().map(|&v| v.count_ones()).sum()
218    }
219
220    #[must_use]
221    pub fn part2(&self) -> &'static str {
222        "🎄"
223    }
224}
225
226examples!(Day25 -> (u32, &'static str) [
227    {file: "day25_example0.txt", part1: 3},
228]);