-
Notifications
You must be signed in to change notification settings - Fork 4
/
day08.rs
168 lines (149 loc) · 6.52 KB
/
day08.rs
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! # Treetop Tree House
//!
//! Part 1 is solved with an efficient `O(n)` algorithm. Part 2 is also solved with an efficient `O(n)`
//! algorithm, using a bit manipulation trick to make the complexity independent of the number of digits.
const ONES: u64 = 0x0041041041041041;
const MASK: u64 = 0x0fffffffffffffc0;
type Input = (usize, Vec<i8>);
/// Convert a 2D grid of ASCII digits into a 1D `vec` of heights.
///
/// Each height is multipled by 6. For part 1 this makes no difference, but for part 2 this helps
/// with the bit manipulation.
///
/// To convert from 2D co-ordinates to an index, the formula is `y * width + x`. For the sample grid
/// of width 5, the top middle point at `(2, 0)` is at index `0 * 5 + 2 = 2` and the point directly
/// below `(2, 1)` is at index `1 * 5 + 2 = 7`.
///
/// Using a 1D `vec` instead of a `vec` of `vec`s is faster for 2 reasons:
/// * Avoids an intermediate pointer lookup for each access.
/// * Better cache locality as the memory locations are adjacent and not potentially
/// scattered all over the heap.
pub fn parse(input: &str) -> Input {
let raw: Vec<_> = input.lines().collect();
let width = raw[0].len();
let mut digits = Vec::new();
for line in &raw {
let iter = line.bytes().map(|b| 6 * (b - b'0') as i8);
digits.extend(iter);
}
(width, digits)
}
/// Calculate visible trees using a rolling maximum for each row and column in left, right, up
/// and down directions.
///
/// Using the top row of the sample and going left to right:
///
/// | Tree | Max | Visible |
/// |---|---|---|
/// | 3 | -1 | true |
/// | 0 | 3 | false |
/// | 3 | 3 | false
/// | 7 | 3 | true |
///
/// The last tree in each row and column doesn't need to be checked since it's covered
/// by the loop in the opposite direction.
///
/// A tree is visible if it can be seen from any direction. As a minor optimization, rather
/// than have 4 separate loops pairs, the left, right, up and down loops are all rolled into
/// one pair, to amortise the cost of loop logic.
///
/// The 4 corners trees don't need to be checked since they're always visible
/// so they're added directly to the total.
pub fn part1(input: &Input) -> usize {
let (width, digits) = input;
let mut visible = vec![false; digits.len()];
for i in 1..(*width - 1) {
let mut left_max = -1;
let mut right_max = -1;
let mut top_max = -1;
let mut bottom_max = -1;
for j in 0..(*width - 1) {
let left = (i * width) + j;
if digits[left] > left_max {
visible[left] = true;
left_max = digits[left];
}
let right = (i * width) + (width - j - 1);
if digits[right] > right_max {
visible[right] = true;
right_max = digits[right];
}
let top = (j * width) + i;
if digits[top] > top_max {
visible[top] = true;
top_max = digits[top];
}
let bottom = (width - j - 1) * width + i;
if digits[bottom] > bottom_max {
visible[bottom] = true;
bottom_max = digits[bottom];
}
}
}
4 + visible.iter().filter(|&&b| b).count()
}
/// Calculate the distance visible in each direction by using 10 rolling maximum values for each
/// height packed into a single `u64`.
///
/// Part 2 is similar to part 1, but instead of keeping a single maximum for each direction, we
/// need to keep an *array* of 10 values, one for each possible height.
///
/// For each tree its score is the current value at the same index as its height. Then we increment
/// the value of previously seen trees greater than the current height by one
/// and reset the values of trees less than or equal than the current height to one.
///
/// We skip processing the edge and corner trees. Strictly speaking their score should be zero, but
/// as the maximum value will always be greater than or equal to one, it's fine to leave them
/// bulk initialized to one.
///
/// Using the fourth row of the sample and going left to right:
///
/// | Tree | Scenic Array | Score |
/// |---|---|---|
/// | 3 | [1, 1, 1, (1), 1, 1, 1, 1, 1, 1] | 1
/// | 5 | [1, 1, 1, 1, 2, (2), 2, 2, 2, 2] | 2
/// | 4 | [1, 1, 1, 1, (1), 1, 3, 3, 3, 3] | 1
///
/// Instead of using an array and iterating over it to update the values, we can achieve the same
/// result much faster by packing the ten values into a single `u64` in blocks of 6 bits. 6 bits
/// gives a maximum value of `2^6 = 64` that's a bit of a gamble. It's less than the maximum
/// possible value of 98 that could be theoretically encountered but should work for most inputs.
///
/// To obtain the current value we right shift by the current height times 6 (this is why we
/// multiplied by 6 in the [`parse`] function) and mask only the least significant 6 bits.
///
/// To update the next value, we first use [`MASK`] left shifted to zero out all bits less than
/// or equal to the current height then add 1 to all values in parallel using the [`ONES`] pattern.
///
/// For example going from 3 to 5 in the sample above:
/// ```none
/// scenic: 000001_000001_000001_000001_000001_000001_000001_000001_000001_000001
/// mask: 111111_111111_111111_111111_111111_111111_000000_000000_000000_000000
/// scenic & mask: 000001_000001_000001_000001_000001_000001_000000_000000_000000_000000
/// scenic + ones: 000010_000010_000010_000010_000010_000010_000001_000001_000001_000001
/// ```
pub fn part2(input: &Input) -> u64 {
let (width, digits) = input;
let mut scenic = vec![1; digits.len()];
for i in 1..(*width - 1) {
let mut left_max = ONES;
let mut right_max = ONES;
let mut top_max = ONES;
let mut bottom_max = ONES;
for j in 1..(*width - 1) {
let left = (i * width) + j;
scenic[left] *= (left_max >> digits[left]) & 0x3f;
left_max = (left_max & (MASK << digits[left])) + ONES;
let right = (i * width) + (width - j - 1);
scenic[right] *= (right_max >> digits[right]) & 0x3f;
right_max = (right_max & (MASK << digits[right])) + ONES;
let top = (j * width) + i;
scenic[top] *= (top_max >> digits[top]) & 0x3f;
top_max = (top_max & (MASK << digits[top])) + ONES;
let bottom = (width - j - 1) * width + i;
scenic[bottom] *= (bottom_max >> digits[bottom]) & 0x3f;
bottom_max = (bottom_max & (MASK << digits[bottom])) + ONES;
}
}
*scenic.iter().max().unwrap()
}