Skip to content

Commit d6b487a

Browse files
committed
2018 day 22
1 parent 421df70 commit d6b487a

File tree

4 files changed

+294
-0
lines changed

4 files changed

+294
-0
lines changed

crates/utils/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub mod multithreading;
1717
pub mod multiversion;
1818
pub mod number;
1919
pub mod parser;
20+
pub mod queue;
2021
pub mod simd;
2122
pub mod slice;
2223
#[cfg(target_family = "wasm")]

crates/utils/src/queue.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
//! Queue data structures.
2+
3+
use std::fmt::{Debug, Formatter};
4+
5+
/// A priority queue using a circular array of buckets for priorities within a sliding window.
6+
///
7+
/// New items must have a priority within `current_priority..current_priority + N`.
8+
/// `current_priority` is only updated on pop and when the first item is pushed.
9+
///
10+
/// Items are popped in LIFO order within each priority bucket as [`Vec`] is used internally to
11+
/// avoid the extra overhead of [`VecDeque`](std::collections::VecDeque).
12+
///
13+
/// # Example
14+
/// ```
15+
/// # use utils::queue::BucketQueue;
16+
/// let mut queue = BucketQueue::<i32, 8>::new();
17+
/// queue.push(0, 100);
18+
/// queue.push(2, 200);
19+
/// queue.push(7, 400);
20+
/// queue.push(2, 300);
21+
/// assert_eq!(queue.pop(), Some(100));
22+
/// assert_eq!(queue.pop(), Some(300));
23+
/// assert_eq!(queue.pop(), Some(200));
24+
/// assert_eq!(queue.peek(), Some(&400));
25+
/// assert_eq!(queue.peek_entry(), Some((7, &400)));
26+
/// assert_eq!(queue.pop_entry(), Some((7, 400)));
27+
/// assert_eq!(queue.pop(), None);
28+
/// assert_eq!(queue.peek(), None);
29+
/// ```
30+
#[must_use]
31+
#[derive(Clone)]
32+
pub struct BucketQueue<T, const N: usize> {
33+
buckets: [Vec<T>; N],
34+
current_priority: usize,
35+
size: usize,
36+
}
37+
38+
impl<T, const N: usize> BucketQueue<T, N> {
39+
#[inline]
40+
pub fn new() -> Self {
41+
Self::default()
42+
}
43+
44+
#[inline]
45+
pub fn with_capacity(per_bucket_capacity: usize) -> Self {
46+
BucketQueue {
47+
buckets: std::array::from_fn(|_| Vec::with_capacity(per_bucket_capacity)),
48+
current_priority: 0,
49+
size: 0,
50+
}
51+
}
52+
53+
#[inline]
54+
pub fn push(&mut self, priority: usize, value: T) {
55+
if self.size == 0 {
56+
self.current_priority = priority;
57+
} else {
58+
assert!(
59+
priority >= self.current_priority && priority < self.current_priority + N,
60+
"priority {priority} out of range {}..{}",
61+
self.current_priority,
62+
self.current_priority + N,
63+
);
64+
}
65+
66+
self.buckets[priority % N].push(value);
67+
self.size += 1;
68+
}
69+
70+
#[inline]
71+
#[must_use]
72+
pub fn pop(&mut self) -> Option<T> {
73+
self.pop_entry().map(|(_, v)| v)
74+
}
75+
76+
#[inline]
77+
#[must_use]
78+
pub fn pop_entry(&mut self) -> Option<(usize, T)> {
79+
if self.size == 0 {
80+
return None;
81+
}
82+
83+
loop {
84+
let idx = self.current_priority % N;
85+
if let Some(value) = self.buckets[idx].pop() {
86+
self.size -= 1;
87+
return Some((self.current_priority, value));
88+
}
89+
self.current_priority += 1;
90+
}
91+
}
92+
93+
#[inline]
94+
#[must_use]
95+
pub fn peek(&self) -> Option<&T> {
96+
self.peek_entry().map(|(_, v)| v)
97+
}
98+
99+
#[inline]
100+
#[must_use]
101+
pub fn peek_entry(&self) -> Option<(usize, &T)> {
102+
if self.size == 0 {
103+
return None;
104+
}
105+
106+
// peek takes &self so can't advance current_priority
107+
for priority in self.current_priority..self.current_priority + N {
108+
if let Some(value) = self.buckets[priority % N].last() {
109+
return Some((priority, value));
110+
}
111+
}
112+
unreachable!()
113+
}
114+
115+
#[inline]
116+
pub fn clear(&mut self) {
117+
self.buckets.iter_mut().for_each(Vec::clear);
118+
self.size = 0;
119+
}
120+
121+
#[inline]
122+
#[must_use]
123+
pub fn len(&self) -> usize {
124+
self.size
125+
}
126+
127+
#[inline]
128+
#[must_use]
129+
pub fn is_empty(&self) -> bool {
130+
self.size == 0
131+
}
132+
}
133+
134+
impl<T, const N: usize> Default for BucketQueue<T, N> {
135+
#[inline]
136+
fn default() -> Self {
137+
BucketQueue {
138+
buckets: std::array::from_fn(|_| Vec::new()),
139+
current_priority: 0,
140+
size: 0,
141+
}
142+
}
143+
}
144+
145+
impl<T: Debug, const N: usize> Debug for BucketQueue<T, N> {
146+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
147+
f.debug_list()
148+
.entries(
149+
(self.current_priority..self.current_priority + N).flat_map(|priority| {
150+
self.buckets[priority % N]
151+
.iter()
152+
.rev() // Match LIFO order within buckets
153+
.map(move |value| (priority, value))
154+
}),
155+
)
156+
.finish()
157+
}
158+
}

crates/year2018/src/day22.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
use utils::prelude::*;
2+
use utils::queue::BucketQueue;
3+
4+
/// Searching a cave with different tools.
5+
#[derive(Clone, Debug)]
6+
pub struct Day22 {
7+
depth: u32,
8+
target_x: usize,
9+
target_y: usize,
10+
}
11+
12+
impl Day22 {
13+
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
14+
let (depth, target_x, target_y) = parser::u32()
15+
.with_prefix("depth: ")
16+
.with_eol()
17+
.then(parser::number_range(0..=31).with_prefix("target: "))
18+
.then(parser::number_range(0..=1021).with_prefix(b','))
19+
.parse_complete(input)?;
20+
21+
Ok(Self {
22+
depth,
23+
target_x,
24+
target_y,
25+
})
26+
}
27+
28+
#[must_use]
29+
pub fn part1(&self) -> u32 {
30+
self.scan::<0>(self.target_x + 1, self.target_y + 1)
31+
.iter()
32+
.sum()
33+
}
34+
35+
#[must_use]
36+
pub fn part2(&self) -> u32 {
37+
let width = self.target_x + 75;
38+
let height = self.target_y + 75;
39+
40+
// Pad the edges of the grid with [0; 3] to avoid needing bounds checks (as 0 is always less
41+
// than next_time)
42+
let mut grid = vec![[u32::MAX; 3]; width * height];
43+
for x in 0..width {
44+
grid[x] = [0; 3];
45+
grid[(height - 1) * width + x] = [0; 3];
46+
}
47+
for y in 0..height {
48+
grid[y * width] = [0; 3];
49+
grid[(y + 1) * width - 1] = [0; 3];
50+
}
51+
52+
// Tool indices match the region type where they're invalid (torch = 1 = wet).
53+
// Set invalid tools for each region to 0.
54+
let scan = self.scan::<1>(width, height);
55+
for (times, &region_type) in grid.iter_mut().zip(scan.iter()) {
56+
times[region_type as usize] = 0;
57+
}
58+
59+
let start_index = width + 1;
60+
let target_index = (self.target_y + 1) * width + self.target_x + 1;
61+
62+
let mut queue = BucketQueue::<_, 8>::with_capacity(256);
63+
// Pack the tool into the bottom 2 bits in the queue entry
64+
queue.push(0, start_index << 2 | 1);
65+
grid[start_index][1] = 0;
66+
67+
while let Some((time, packed)) = queue.pop_entry() {
68+
let (time, index, tool) = (time as u32, packed >> 2, packed & 3);
69+
70+
if index == target_index && tool == 1 {
71+
return time;
72+
}
73+
74+
if grid[index][tool] != time {
75+
continue;
76+
}
77+
78+
let region_type = scan[index] as usize;
79+
let other_tool = 3 - tool - region_type;
80+
81+
for (next_index, next_tool, next_time) in [
82+
(index - 1, tool, time + 1),
83+
(index + 1, tool, time + 1),
84+
(index - width, tool, time + 1),
85+
(index + width, tool, time + 1),
86+
(index, other_tool, time + 7),
87+
] {
88+
if grid[next_index][next_tool] > next_time {
89+
grid[next_index][next_tool] = next_time;
90+
queue.push(next_time as usize, next_index << 2 | next_tool);
91+
}
92+
}
93+
}
94+
95+
panic!("no solution found")
96+
}
97+
98+
fn scan<const PADDING: usize>(&self, width: usize, height: usize) -> Vec<u32> {
99+
// Padding is used by part2 to avoid bounds checks in the main time grid. Also padding
100+
// the scan grid keeps indexes consistent between the two grids.
101+
let inner_width = width - 2 * PADDING;
102+
let inner_height = height - 2 * PADDING;
103+
let target_index = (self.target_y + PADDING) * width + self.target_x + PADDING;
104+
105+
let mut erosion = vec![0u32; width * height];
106+
107+
let base = PADDING * width + PADDING;
108+
for (x, e) in erosion[base..base + inner_width].iter_mut().enumerate() {
109+
*e = ((x as u32 * 16807) + self.depth) % 20183;
110+
}
111+
112+
for y in 1..inner_height {
113+
let base = (y + PADDING) * width + PADDING;
114+
115+
let mut prev = ((y as u32 * 48271) + self.depth) % 20183;
116+
erosion[base] = prev;
117+
118+
for index in base + 1..base + inner_width {
119+
if index == target_index {
120+
prev = self.depth % 20183;
121+
} else {
122+
prev = ((prev * erosion[index - width]) + self.depth) % 20183;
123+
}
124+
erosion[index] = prev;
125+
}
126+
}
127+
128+
erosion.into_iter().map(|x| x % 3).collect()
129+
}
130+
}
131+
132+
examples!(Day22 -> (u32, u32) [
133+
{input: "depth: 510\ntarget: 10,10", part1: 114, part2: 45},
134+
]);

crates/year2018/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ utils::year!(2018 => year2018, ${
2525
19 => day19::Day19,
2626
20 => day20::Day20,
2727
21 => day21::Day21,
28+
22 => day22::Day22,
2829
});

0 commit comments

Comments
 (0)