Skip to content

Commit 77edc50

Browse files
committed
2016 day 22
1 parent 3c48f16 commit 77edc50

File tree

3 files changed

+140
-0
lines changed

3 files changed

+140
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Filesystem Size Used Avail Use%
2+
/dev/grid/node-x0-y0 10T 8T 2T 80%
3+
/dev/grid/node-x0-y1 11T 6T 5T 54%
4+
/dev/grid/node-x0-y2 32T 28T 4T 87%
5+
/dev/grid/node-x1-y0 9T 7T 2T 77%
6+
/dev/grid/node-x1-y1 8T 0T 8T 0%
7+
/dev/grid/node-x1-y2 11T 7T 4T 63%
8+
/dev/grid/node-x2-y0 10T 6T 4T 60%
9+
/dev/grid/node-x2-y1 9T 8T 1T 88%
10+
/dev/grid/node-x2-y2 9T 6T 3T 66%

crates/year2016/src/day22.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use utils::point::Point2D;
2+
use utils::prelude::*;
3+
4+
/// Solving a sliding puzzle.
5+
///
6+
/// The solution assumes the input has one empty node, similar to the
7+
/// [15 puzzle](https://en.wikipedia.org/wiki/15_puzzle), as well as one immovable wall on the right
8+
/// (or immovable nodes below the empty node, like the example input).
9+
#[derive(Clone, Debug)]
10+
pub struct Day22 {
11+
max_x: u32,
12+
max_y: u32,
13+
wall_x: u32,
14+
empty: Point2D<u32>,
15+
}
16+
17+
#[derive(Copy, Clone, Debug)]
18+
struct Node {
19+
x: u32,
20+
y: u32,
21+
used: u32,
22+
avail: u32,
23+
}
24+
25+
impl Day22 {
26+
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
27+
let Some((_, input)) = input.split_once("Use%") else {
28+
return Err(InputError::new(input, 0, "expected df header"));
29+
};
30+
31+
let nodes = parser::u32()
32+
.with_prefix("/dev/grid/node-x")
33+
.then(parser::u32().with_prefix("-y"))
34+
.then(
35+
parser::u32()
36+
.with_prefix(parser::take_while1(u8::is_ascii_whitespace))
37+
.with_suffix(b'T')
38+
.repeat::<3>(),
39+
)
40+
.map_res(|(x, y, [size, used, avail])| {
41+
if size == used + avail {
42+
Ok(Node { x, y, used, avail })
43+
} else {
44+
Err("expected Used + Avail to equal Size")
45+
}
46+
})
47+
.with_suffix(parser::take_while1(u8::is_ascii_whitespace))
48+
.with_suffix(parser::number_range(0..=100))
49+
.with_suffix("%")
50+
.parse_lines(input.trim_ascii_start())?;
51+
52+
let max_x = nodes.iter().map(|n| n.x).max().unwrap();
53+
let max_y = nodes.iter().map(|n| n.y).max().unwrap();
54+
if ((max_x + 1) * (max_y + 1)) as usize != nodes.len() {
55+
return Err(InputError::new(input, 0, "expected rectangular grid"));
56+
}
57+
58+
// Check input has a single empty node
59+
let (empty, mut non_empty): (Vec<_>, Vec<_>) = nodes.into_iter().partition(|n| n.used == 0);
60+
let [empty] = empty[..] else {
61+
return Err(InputError::new(input, 0, "expected one empty node"));
62+
};
63+
64+
// Check no viable pairs can be formed between two non-empty nodes
65+
let min_used = non_empty.iter().map(|n| n.used).min().unwrap_or(0);
66+
let max_available = non_empty.iter().map(|n| n.avail).max().unwrap_or(0);
67+
if min_used < max_available {
68+
return Err(InputError::new(
69+
input,
70+
0,
71+
"expected the maximum available space on non-empty nodes to be less than \
72+
the minimum used space on the non-empty nodes",
73+
));
74+
}
75+
76+
// Check immovable nodes
77+
non_empty.retain(|n| n.used > empty.avail);
78+
non_empty.sort_unstable_by_key(|n| (n.y, n.x));
79+
let wall_x = if !non_empty.is_empty() && non_empty[0].y < empty.y {
80+
// Immovable nodes above empty node (real input), check they form a single wall
81+
if non_empty.iter().any(|n| n.y != non_empty[0].y)
82+
|| non_empty.windows(2).any(|w| w[0].x + 1 != w[1].x)
83+
|| non_empty[0].x == 0
84+
|| non_empty[0].y < 2
85+
|| non_empty.last().unwrap().x != max_x
86+
{
87+
return Err(InputError::new(
88+
input,
89+
0,
90+
"expected either a single wall of immovable nodes",
91+
));
92+
}
93+
non_empty[0].x
94+
} else {
95+
// All immovable nodes are below the empty node (which happens in the example input)
96+
// Add a fake to wall to the right as this adds no extra steps and avoids extra logic
97+
empty.x + 1
98+
};
99+
100+
Ok(Self {
101+
max_x,
102+
max_y,
103+
empty: Point2D::new(empty.x, empty.y),
104+
wall_x,
105+
})
106+
}
107+
108+
#[must_use]
109+
pub fn part1(&self) -> u32 {
110+
// All pairs are (empty node, one of the movable nodes), so the number of pairs is the
111+
// number of movable nodes
112+
(self.max_x + 1) * (self.max_y + 1) // All nodes
113+
- (self.max_x - self.wall_x + 1) // Minus immovable nodes
114+
- 1 // Minus empty node
115+
}
116+
117+
#[must_use]
118+
pub fn part2(&self) -> u32 {
119+
(self.empty.x - (self.wall_x - 1)) // Move empty node left to avoid wall
120+
+ self.empty.y // Move empty node up to y=0
121+
+ ((self.max_x - 1) - (self.wall_x - 1)) // Move empty node right to x=(max - 1)
122+
+ ((self.max_x - 1) * 5) // Shuffle goal data to x=1
123+
+ 1 // Move goal data into x=0
124+
}
125+
}
126+
127+
examples!(Day22 -> (u32, u32) [
128+
{file: "day22_example0.txt", part2: 7},
129+
]);

crates/year2016/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ utils::year!(2016 => year2016, ${
2323
19 => day19::Day19,
2424
20 => day20::Day20,
2525
21 => day21::Day21,
26+
22 => day22::Day22,
2627
});

0 commit comments

Comments
 (0)