Skip to content

Commit 0d22566

Browse files
committed
2025: day 12 full solution
1 parent 892b140 commit 0d22566

File tree

1 file changed

+221
-4
lines changed

1 file changed

+221
-4
lines changed

src/year2025/day12/day12.rs

Lines changed: 221 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,222 @@
11
//! [Day 12: Christmas Tree Farm](https://adventofcode.com/2025/day/12)
22
3+
use rayon::prelude::*;
4+
use rustc_hash::{FxHashMap, FxHashSet};
5+
6+
/// A 3x3 present shape.
7+
#[derive(Eq, PartialEq, Hash, Clone)]
8+
struct Shape(Vec<(usize, usize)>);
9+
10+
impl Shape {
11+
const fn new() -> Self {
12+
Self(vec![])
13+
}
14+
15+
fn rotate_clockwise(&self) -> Self {
16+
Self(self.0.iter().map(|&(x, y)| (2 - y, x)).collect())
17+
}
18+
19+
fn flip_horizontal(&self) -> Self {
20+
Self(self.0.iter().map(|&(x, y)| (x, 2 - y)).collect())
21+
}
22+
23+
fn get_all_orientations(&self) -> Vec<Self> {
24+
let mut orientations = FxHashSet::default();
25+
26+
let mut current = self.clone();
27+
for _ in 0..4 {
28+
orientations.insert(current.clone());
29+
orientations.insert(current.flip_horizontal());
30+
current = current.rotate_clockwise();
31+
}
32+
33+
orientations.into_iter().collect()
34+
}
35+
}
36+
37+
/// The region modelized as a grid.
38+
struct Grid {
39+
g: Vec<Vec<usize>>,
40+
width: usize,
41+
height: usize,
42+
}
43+
44+
impl Grid {
45+
fn new(width: usize, height: usize) -> Self {
46+
Self {
47+
g: vec![vec![0usize; width]; height],
48+
width,
49+
height,
50+
}
51+
}
52+
fn can_place(&self, cells: &Shape, start_x: usize, start_y: usize) -> bool {
53+
for &(dx, dy) in &cells.0 {
54+
let x = start_x + dx;
55+
let y = start_y + dy;
56+
57+
if y >= self.height || x >= self.width {
58+
return false;
59+
}
60+
if self.g[y][x] != 0 {
61+
return false;
62+
}
63+
}
64+
true
65+
}
66+
67+
fn place(&mut self, cells: &Shape, start_x: usize, start_y: usize, value: usize) {
68+
for &(dy, dx) in &cells.0 {
69+
let x = start_x + dx;
70+
let y = start_y + dy;
71+
self.g[y][x] = value;
72+
}
73+
}
74+
75+
fn remove(&mut self, cells: &Shape, start_x: usize, start_y: usize) {
76+
for &(dy, dx) in &cells.0 {
77+
let x = start_x + dx;
78+
let y = start_y + dy;
79+
self.g[y][x] = 0;
80+
}
81+
}
82+
83+
fn backtrack(&mut self, idx: usize, pieces: &Vec<Vec<Shape>>) -> bool {
84+
if idx == pieces.len() {
85+
return true;
86+
}
87+
88+
for cells in &pieces[idx] {
89+
let max_x = cells.0.iter().map(|&(x, _)| x).max().unwrap_or(0);
90+
let max_y = cells.0.iter().map(|&(_, y)| y).max().unwrap_or(0);
91+
92+
if max_x >= self.width {
93+
continue;
94+
}
95+
if max_y >= self.height {
96+
continue;
97+
}
98+
99+
let max_start_x = self.width - max_y - 1;
100+
let max_start_y = self.height - max_x - 1;
101+
102+
for start_y in 0..=max_start_y {
103+
for start_x in 0..=max_start_x {
104+
if self.can_place(cells, start_x, start_y) {
105+
self.place(cells, start_x, start_y, idx + 1);
106+
if self.backtrack(idx + 1, pieces) {
107+
return true;
108+
}
109+
self.remove(cells, start_x, start_y);
110+
}
111+
}
112+
}
113+
}
114+
115+
false
116+
}
117+
}
118+
119+
struct Puzzle {
120+
shapes: FxHashMap<usize, Shape>,
121+
regions: Vec<(usize, usize, Vec<usize>)>,
122+
}
123+
124+
impl Puzzle {
125+
fn new(data: &str) -> Self {
126+
let mut shapes = FxHashMap::default();
127+
let mut regions = vec![];
128+
129+
for s in data.split("\n\n") {
130+
// a present shape
131+
if s.contains('#') {
132+
let lines: Vec<_> = s.lines().collect();
133+
134+
let idx = lines[0].strip_suffix(':').unwrap().parse().unwrap();
135+
136+
let mut shape = Shape::new();
137+
for y in 0..3usize {
138+
for x in 0..3usize {
139+
if lines[y + 1].chars().nth(x).unwrap() == '#' {
140+
shape.0.push((x, y));
141+
}
142+
}
143+
}
144+
145+
shapes.insert(idx, shape);
146+
}
147+
148+
// regions under the tree
149+
if s.contains('x') {
150+
for line in s.lines() {
151+
let (size, counts) = line.split_once(": ").unwrap();
152+
let (width, height) = size.split_once('x').unwrap();
153+
154+
regions.push((
155+
width.parse().unwrap(),
156+
height.parse().unwrap(),
157+
counts
158+
.split_ascii_whitespace()
159+
.map(|s| s.parse().unwrap())
160+
.collect(),
161+
));
162+
}
163+
}
164+
}
165+
166+
Self { shapes, regions }
167+
}
168+
169+
/// Try to fit pieces into a region (width x height) using backtracking.
170+
fn solve_region(&self, width: usize, height: usize, counts: &[usize]) -> bool {
171+
let mut pieces = vec![];
172+
173+
for (shape_idx, &count) in counts.iter().enumerate() {
174+
if count == 0 {
175+
continue;
176+
}
177+
if let Some(shape) = self.shapes.get(&shape_idx) {
178+
let orientations = shape.get_all_orientations();
179+
let size = shape.0.len();
180+
for _ in 0..count {
181+
pieces.push((size, orientations.clone()));
182+
}
183+
}
184+
}
185+
186+
if pieces.is_empty() {
187+
return true;
188+
}
189+
190+
let total_cells: usize = pieces.iter().map(|(s, _)| *s).sum();
191+
if total_cells > width * height {
192+
return false;
193+
}
194+
195+
pieces.sort_by(|a, b| b.0.cmp(&a.0));
196+
let pieces_orientations: Vec<Vec<Shape>> = pieces.into_iter().map(|(_, o)| o).collect();
197+
198+
let mut grid = Grid::new(width, height);
199+
grid.backtrack(0, &pieces_orientations)
200+
}
201+
202+
fn part1(&self) -> usize {
203+
self.regions
204+
.par_iter()
205+
.map(|(width, height, counts)| usize::from(self.solve_region(*width, *height, counts)))
206+
.sum()
207+
}
208+
}
209+
3210
/// # Panics
4211
#[must_use]
5212
pub fn solve(data: &str) -> (usize, aoc::Christmas) {
213+
let puzzle = Puzzle::new(data);
214+
(puzzle.part1(), aoc::CHRISTMAS)
215+
}
216+
217+
/// # Panics
218+
#[must_use]
219+
pub fn solve_dummy(data: &str) -> (usize, aoc::Christmas) {
6220
let part1 = data
7221
.split("\n\n")
8222
.map(|s| {
@@ -34,7 +248,11 @@ pub fn solve(data: &str) -> (usize, aoc::Christmas) {
34248

35249
pub fn main() {
36250
let args = aoc::parse_args();
37-
args.run(solve);
251+
if args.has_option("--dummy") {
252+
args.run(solve_dummy);
253+
} else {
254+
args.run(solve);
255+
}
38256
}
39257

40258
#[cfg(test)]
@@ -45,8 +263,7 @@ mod test {
45263

46264
#[test]
47265
fn part1() {
48-
let (part1, _) = solve(TEST_INPUT);
49-
assert_ne!(part1, 0); // dummy solver does not work for test input
50-
// assert_eq!(part1, 3);
266+
let puzzle = Puzzle::new(TEST_INPUT);
267+
assert_eq!(puzzle.part1(), 3);
51268
}
52269
}

0 commit comments

Comments
 (0)