-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathday05.rs
More file actions
110 lines (93 loc) · 3.16 KB
/
day05.rs
File metadata and controls
110 lines (93 loc) · 3.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
//! # A Maze of Twisty Trampolines, All Alike
//!
//! Part one brute forces the jumps. For part two, we can make an observation that the jump offsets
//! will eventually flip-flop between 2 or 3 starting from the beginning, for example:
//!
//! ```none
//! 2 3 2 3 -1
//! ```
//!
//! The twos and threes can be represented in binary compact form, using 0 for 2 and 1 for 3:
//!
//! ```none
//! 0101
//! ```
//!
//! We then precompute all possible combinations for blocks of width 16,
//! using this to accelerate part two.
use crate::util::parse::*;
use std::array::from_fn;
pub fn parse(input: &str) -> Vec<i32> {
input.iter_signed().collect()
}
/// Brute force implementation.
pub fn part1(input: &[i32]) -> usize {
let mut jump = input.to_vec();
let mut total = 0;
let mut index = 0;
while index < jump.len() {
let next = index.wrapping_add(jump[index] as usize);
jump[index] += 1;
total += 1;
index = next;
}
total
}
pub fn part2(input: &[i32]) -> usize {
let mut jump = input.to_vec();
let mut total = 0;
let mut index = 0;
let mut fine = 0;
let mut coarse = 0;
let mut compact = Vec::new();
// Precompute all possible combinations for each binary starting number from 0 to 2¹⁶,
// starting at any offset from 0..2.
let cache: Vec<[_; 0x10000]> =
(0..3).map(|offset| from_fn(|value| compute_block(value, offset))).collect();
while index < jump.len() {
if index < coarse {
if index % 16 >= 3 {
let j = index / 16;
let (next, steps, delta) = compute_block(compact[j], index % 16);
compact[j] = next as usize;
total += steps as usize;
index += delta as usize;
}
// Index lies within precomputed blocks.
for value in &mut compact[(index / 16)..(coarse / 16)] {
let (next, steps, delta) = cache[index % 16][*value];
*value = next as usize;
total += steps as usize;
index += delta as usize;
}
} else {
// Fall back to part one approach.
let next = index.wrapping_add(jump[index] as usize);
jump[index] += if jump[index] == 3 { -1 } else { 1 };
total += 1;
// The frontier of twos and threes advances through the jump offsets.
// Each time it crosses a block of 16 add to the compact binary representation.
if jump[index] == 2 && index == fine {
fine += 1;
if fine.is_multiple_of(16) {
let value = (coarse..fine).rev().fold(0, |acc, i| (acc << 1) | (jump[i] & 1));
coarse = fine;
compact.push(value as usize);
}
}
index = next;
}
}
total
}
#[inline]
fn compute_block(mut value: usize, mut offset: usize) -> (u16, u8, u8) {
let start = offset;
let mut steps = 0;
while offset < 16 {
value ^= 1 << offset;
steps += 1;
offset += 3 - ((value >> offset) & 1);
}
(value as u16, steps, (offset - start) as u8)
}