Skip to content

Commit 44e7a24

Browse files
committed
2017 day 25
1 parent ac9a83f commit 44e7a24

File tree

3 files changed

+250
-0
lines changed

3 files changed

+250
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Begin in state A.
2+
Perform a diagnostic checksum after 6 steps.
3+
4+
In state A:
5+
If the current value is 0:
6+
- Write the value 1.
7+
- Move one slot to the right.
8+
- Continue with state B.
9+
If the current value is 1:
10+
- Write the value 0.
11+
- Move one slot to the left.
12+
- Continue with state B.
13+
14+
In state B:
15+
If the current value is 0:
16+
- Write the value 1.
17+
- Move one slot to the left.
18+
- Continue with state A.
19+
If the current value is 1:
20+
- Write the value 1.
21+
- Move one slot to the right.
22+
- Continue with state A.

crates/year2017/src/day25.rs

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
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+
]);

crates/year2017/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ utils::year!(2017 => year2017, ${
2828
22 => day22::Day22,
2929
23 => day23::Day23,
3030
24 => day24::Day24,
31+
25 => day25::Day25,
3132
});

0 commit comments

Comments
 (0)