44//! and tape value at the cursor. Each transition is then computed via a lookup into this array.
55//!
66//! To speed things up by about ten times, multiple transitions are then precomputed to allow
7- //! skipping forward multiple steps at a time.
7+ //! skipping forward multiple steps at a time. Blocks 128 cells wide are cached once the cursor
8+ //! moves off either end.
89//!
9- //! For each of the 6 * 256 = 1536 combinations of a tape with 4 values to the left and 3 values
10- //! to the right of the cursor, the turing machine is computed one steps at a time until the cursor
11- //! leaves the area to the left or to the right. For example:
12- //!
13- //! ```none
14- //! State: A => State: B
15- //! 1 0 1 0 [1] 0 0 1 [0] 0 1 0 0 1 1 0
16- //!
17- //! State: B
18- //! t t t t [0] 0 1 0
19- //! ```
20- //!
21- //! In this example the tape then advances four bits to the left, loading the four values of `t`
22- //! then the next batch lookup is performed.
10+ //! Interestingly the total number of distinct cached blocks is very low, approximately 200.
11+ //! The cursor also doesn't move too far, only covering a range of about 6,000 steps.
12+ use crate :: util:: hash:: * ;
2313use crate :: util:: parse:: * ;
24- use std:: array:: from_fn;
2514
2615pub struct Input {
2716 state : usize ,
@@ -46,9 +35,8 @@ impl Rule {
4635
4736struct Skip {
4837 next_state : usize ,
49- next_tape : usize ,
38+ next_tape : u128 ,
5039 steps : u32 ,
51- ones : i32 ,
5240 advance : bool ,
5341}
5442
@@ -66,63 +54,61 @@ pub fn parse(input: &str) -> Input {
6654 Input { state, steps, rules }
6755}
6856
69- pub fn part1 ( input : & Input ) -> i32 {
70- // Precompute state transitions in larger amount in order to skip forward several transitions
71- // at a time. 100 max_steps is a safety valve in case some state transitions do not halt
72- // although this is unlikely for the inputs that are provided.
73- let table: Vec < [ Skip ; 256 ] > = ( 0 ..input. rules . len ( ) )
74- . map ( |state| from_fn ( |tape| turing ( & input. rules , state, tape, 100 ) ) )
75- . collect ( ) ;
76-
57+ pub fn part1 ( input : & Input ) -> u32 {
7758 let mut state = input. state ;
7859 let mut remaining = input. steps ;
7960 let mut tape = 0 ;
80- let mut checksum = 0 ;
8161 let mut left = Vec :: new ( ) ;
8262 let mut right = Vec :: new ( ) ;
63+ let mut cache = FastMap :: new ( ) ;
8364
8465 loop {
8566 // Lookup the next batch state transition.
86- let Skip { next_state, next_tape, steps, ones, advance } = table[ state] [ tape] ;
67+ let Skip { next_state, next_tape, steps, advance } = * cache
68+ . entry ( ( state, tape) )
69+ . or_insert_with ( || turing ( & input. rules , state, tape, u32:: MAX ) ) ;
8770
8871 // Handle any remaining transitions less than the batch size one step at a time.
8972 if steps > remaining {
90- let Skip { ones, .. } = turing ( & input. rules , state, tape, remaining) ;
91- break checksum + ones;
73+ let Skip { next_tape, .. } = turing ( & input. rules , state, tape, remaining) ;
74+ tape = next_tape;
75+ break ;
9276 }
9377
9478 state = next_state;
9579 tape = next_tape;
9680 remaining -= steps;
97- checksum += ones;
9881
9982 // Use a vector to simulate an empty tape. In practice the cursor doesn't move more than
10083 // a few thousand steps in any direction, so this approach is as fast as a fixed size
10184 // array, but much more robust.
10285 if advance {
103- left. push ( tape & 0xf0 ) ;
104- tape = ( ( tape & 0xf ) << 4 ) | right. pop ( ) . unwrap_or ( 0 ) ;
86+ left. push ( tape & 0xffffffffffffffff0000000000000000 ) ;
87+ tape = ( tape << 64 ) | right. pop ( ) . unwrap_or ( 0 ) ;
10588 } else {
106- right. push ( tape & 0xf ) ;
107- tape = ( tape >> 4 ) | left. pop ( ) . unwrap_or ( 0 ) ;
89+ right. push ( tape & 0x0000000000000000ffffffffffffffff ) ;
90+ tape = ( tape >> 64 ) | left. pop ( ) . unwrap_or ( 0 ) ;
10891 }
10992 }
93+
94+ tape. count_ones ( )
95+ + left. iter ( ) . map ( |& n| n. count_ones ( ) ) . sum :: < u32 > ( )
96+ + right. iter ( ) . map ( |& n| n. count_ones ( ) ) . sum :: < u32 > ( )
11097}
11198
11299pub fn part2 ( _input : & Input ) -> & ' static str {
113100 "n/a"
114101}
115102
116103// Precompute state transitions up to some maximum value of steps.
117- fn turing ( rules : & [ [ Rule ; 2 ] ] , mut state : usize , mut tape : usize , max_steps : u32 ) -> Skip {
118- let mut mask = 0b00001000 ;
104+ fn turing ( rules : & [ [ Rule ; 2 ] ] , mut state : usize , mut tape : u128 , max_steps : u32 ) -> Skip {
105+ let mut mask = 1 << 63 ;
119106 let mut steps = 0 ;
120- let mut ones = 0 ;
121107
122- // `0`` means the cursor has advanced to the next nibble on the right.
108+ // `0` means the cursor has advanced to the next nibble on the right.
123109 // `128` means that the cursor is on the left edge of the high nibble.
124- while 0 < mask && mask < 128 && steps < max_steps {
125- let current = ( ( tape & mask) != 0 ) as usize ;
110+ while 0 < mask && mask < ( 1 << 127 ) && steps < max_steps {
111+ let current = usize :: from ( tape & mask != 0 ) ;
126112 let Rule { next_state, next_tape, advance } = rules[ state] [ current] ;
127113
128114 if next_tape == 1 {
@@ -139,10 +125,7 @@ fn turing(rules: &[[Rule; 2]], mut state: usize, mut tape: usize, max_steps: u32
139125
140126 state = next_state;
141127 steps += 1 ;
142- // Count the total numbers of ones by summing the number of ones written minus the
143- // number of ones erased.
144- ones += next_tape as i32 - current as i32 ;
145128 }
146129
147- Skip { next_state : state, next_tape : tape, steps, ones , advance : mask == 0 }
130+ Skip { next_state : state, next_tape : tape, steps, advance : mask == 0 }
148131}
0 commit comments