Skip to content

Commit 90523c4

Browse files
committed
solve: day12
1 parent cd3101a commit 90523c4

File tree

3 files changed

+213
-1
lines changed

3 files changed

+213
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
resolver = "2"
33
members = [
44
"aoc_lib/aoc_derive",
5-
"aoc_lib/utils", "day1", "day10", "day11", "day2", "day3", "day4", "day5", "day6", "day7", "day8", "day9",
5+
"aoc_lib/utils", "day1", "day10", "day11", "day12", "day2", "day3", "day4", "day5", "day6", "day7", "day8", "day9",
66
]
77

88
[workspace.dependencies]

day12/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "day12"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
aoc_derive.path = '../aoc_lib/aoc_derive'
8+
utils.path = '../aoc_lib/utils'
9+
derive_more.workspace = true
10+
itertools.workspace = true
11+
lazy-regex.workspace = true
12+
parse-display.workspace = true
13+
rayon.workspace = true
14+
regex.workspace = true
15+
num.workspace = true
16+
17+
[dev-dependencies]
18+
pretty_assertions.workspace = true

day12/src/main.rs

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
use aoc_derive::aoc_main;
2+
use itertools::{Itertools, iproduct};
3+
use lazy_regex::regex;
4+
use utils::RegexHelper;
5+
use utils::grid::Grid;
6+
use utils::math::Vec2D;
7+
use utils::*;
8+
9+
#[derive(Debug, Clone)]
10+
struct Shape {
11+
display: String,
12+
rotations: Vec<Vec<Vec2D>>,
13+
}
14+
15+
impl Shape {
16+
fn new(s: &str) -> Self {
17+
let base = s.lines().skip(1).map(|line| line.chars().collect_vec()).collect_vec();
18+
19+
// Both puzzle input and example have 3x3 shapes, so we can hardcode this
20+
fn rotate(shape: &[Vec<char>]) -> Vec<Vec<char>> {
21+
vec![
22+
vec![shape[2][0], shape[1][0], shape[0][0]],
23+
vec![shape[2][1], shape[1][1], shape[0][1]],
24+
vec![shape[2][2], shape[1][2], shape[0][2]],
25+
]
26+
}
27+
28+
let rot90 = rotate(&base);
29+
let rot180 = rotate(&rot90);
30+
let rot270 = rotate(&rot180);
31+
32+
Self {
33+
display: base.iter().map(|line| line.iter().join("")).join("\n"),
34+
rotations: [base, rot90, rot180, rot270]
35+
.into_iter()
36+
.unique()
37+
.map(|shape| {
38+
shape
39+
.iter()
40+
.enumerate()
41+
.flat_map(|(y, line)| {
42+
line.iter().enumerate().filter_map(move |(x, c)| {
43+
(*c == '#').then_some(Vec2D::from((x, y)))
44+
})
45+
})
46+
.collect_vec()
47+
})
48+
.collect_vec(),
49+
}
50+
}
51+
}
52+
53+
#[derive(Debug)]
54+
struct Region {
55+
width: usize,
56+
height: usize,
57+
shape_req: Vec<usize>,
58+
}
59+
60+
impl Region {
61+
fn new(s: &str) -> Self {
62+
let (width, height, shapes) = regex!(r#"(\d+)x(\d+): (.*)"#).capture_into_tuple(s);
63+
Self {
64+
width: width.parse().unwrap(),
65+
height: height.parse().unwrap(),
66+
shape_req: shapes.split_whitespace().map(|n| n.parse().unwrap()).collect_vec(),
67+
}
68+
}
69+
70+
fn solve(&self, shapes: &[Shape]) -> bool {
71+
self.solve_impl(
72+
self.shape_req.clone(),
73+
&mut vec![],
74+
shapes,
75+
&mut Grid::new(vec![vec!['.'; self.width]; self.height]),
76+
)
77+
}
78+
79+
fn solve_impl(
80+
&self,
81+
shapes_left: Vec<usize>,
82+
insertions: &mut Vec<(usize, Vec2D)>,
83+
available_shapes: &[Shape],
84+
grid: &mut Grid<char>,
85+
) -> bool {
86+
if shapes_left.iter().all(|&n| n == 0) {
87+
return true;
88+
}
89+
90+
if grid.iter().filter(|(_, c)| **c == '.').count()
91+
< shapes_left
92+
.iter()
93+
.enumerate()
94+
.map(|(i, n)| n * available_shapes[i].display.chars().filter(|&c| c == '#').count())
95+
.sum()
96+
{
97+
return false;
98+
}
99+
100+
for (x, y) in iproduct!(0..grid.num_cols() - 2, 0..grid.num_rows() - 2) {
101+
let top_left = Vec2D::from((x, y));
102+
for (i, &num_shapes) in shapes_left.iter().enumerate() {
103+
if num_shapes == 0 {
104+
continue;
105+
}
106+
for rot in &available_shapes[i].rotations {
107+
let fits = rot.iter().all(|&pos| grid[top_left + pos] == '.');
108+
if fits {
109+
for pos in rot {
110+
grid[top_left + *pos] = '#';
111+
}
112+
113+
let mut new_shapes_left = shapes_left.clone();
114+
new_shapes_left[i] -= 1;
115+
116+
insertions.push((i, top_left));
117+
118+
// println!("Inserted shape {i} @{}", top_left);
119+
let solved =
120+
self.solve_impl(new_shapes_left, insertions, available_shapes, grid);
121+
122+
for pos in rot {
123+
grid[top_left + *pos] = '.';
124+
}
125+
insertions.pop();
126+
127+
if solved {
128+
return true;
129+
}
130+
}
131+
}
132+
}
133+
}
134+
135+
false
136+
}
137+
}
138+
139+
#[aoc_main]
140+
fn solve(input: Input) -> impl Into<Solution> {
141+
let blocks = input.blocks().collect_vec();
142+
let (regions, shapes) = blocks.split_last().unwrap();
143+
let shapes = shapes.iter().copied().map(Shape::new).collect_vec();
144+
145+
let regions = regions.lines().map(Region::new);
146+
147+
regions.filter(|r| r.solve(&shapes)).count()
148+
}
149+
150+
#[cfg(test)]
151+
mod tests {
152+
use super::*;
153+
#[test]
154+
fn test_examples() {
155+
use utils::assert_example;
156+
assert_example!(
157+
r#"0:
158+
###
159+
##.
160+
##.
161+
162+
1:
163+
###
164+
##.
165+
.##
166+
167+
2:
168+
.##
169+
###
170+
##.
171+
172+
3:
173+
##.
174+
###
175+
##.
176+
177+
4:
178+
###
179+
#..
180+
###
181+
182+
5:
183+
###
184+
.#.
185+
###
186+
187+
4x4: 0 0 0 0 2 0
188+
12x5: 1 0 1 0 2 2
189+
12x5: 1 0 1 0 3 2
190+
"#,
191+
2
192+
);
193+
}
194+
}

0 commit comments

Comments
 (0)