Skip to content

Commit 64818ec

Browse files
committed
Faster approach caching 128 bit blocks instead of 8 bit blocks
1 parent 1533e69 commit 64818ec

File tree

2 files changed

+29
-46
lines changed

2 files changed

+29
-46
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
323323
| 22 | [Sporifica Virus](https://adventofcode.com/2017/day/22) | [Source](src/year2017/day22.rs) | 17000 |
324324
| 23 | [Coprocessor Conflagration](https://adventofcode.com/2017/day/23) | [Source](src/year2017/day23.rs) | 16 |
325325
| 24 | [Electromagnetic Moat](https://adventofcode.com/2017/day/24) | [Source](src/year2017/day24.rs) | 275 |
326-
| 25 | [The Halting Problem](https://adventofcode.com/2017/day/25) | [Source](src/year2017/day25.rs) | 3698 |
326+
| 25 | [The Halting Problem](https://adventofcode.com/2017/day/25) | [Source](src/year2017/day25.rs) | 1805 |
327327

328328
## 2016
329329

src/year2017/day25.rs

Lines changed: 28 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,13 @@
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::*;
2313
use crate::util::parse::*;
24-
use std::array::from_fn;
2514

2615
pub struct Input {
2716
state: usize,
@@ -46,9 +35,8 @@ impl Rule {
4635

4736
struct 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

11299
pub 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

Comments
 (0)