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]
5212pub 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
35249pub 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