11import re
2+ from collections import namedtuple
3+ from itertools import combinations
24
5+ Point = namedtuple ("Point" , ["x" , "y" ])
6+ BoundingBox = namedtuple ("BoundingBox" , ["bottom_left" , "top_right" ])
37
4- def part_number_count (schematic ):
8+
9+ def intersects (bbox_a , bbox_b ):
10+ # Adapted from https://stackoverflow.com/a/40795835
11+ return not (
12+ bbox_a .top_right .x < bbox_b .bottom_left .x
13+ or bbox_a .bottom_left .x > bbox_b .top_right .x
14+ or bbox_a .top_right .y > bbox_b .bottom_left .y
15+ or bbox_a .bottom_left .y < bbox_b .top_right .y
16+ )
17+
18+
19+ def overlapping_content (bbox_a , bbox_b , rows ):
20+ # Calculate the max bounding box
21+ # x_start = min(bbox_a.bottom_left.x, bbox_b.bottom_left.x)
22+ # x_end = max(bbox_a.top_right.x, bbox_b.top_right.x)
23+ # y_start = min(bbox_a.top_right.y, bbox_b.top_right.y)
24+ # y_end = max(bbox_a.bottom_left.y, bbox_b.bottom_left.y)
25+ #
26+ # overlapping_text = []
27+ # for y in range(y_start, y_end + 1):
28+ # row_content = rows[y]
29+ # overlapping_text.append(row_content[x_start : x_end + 1])
30+ # print("\n".join(overlapping_text))
31+
32+ # Calculate the overlapping region
33+ x_start = max (bbox_a .bottom_left .x , bbox_b .bottom_left .x )
34+ x_end = min (bbox_a .top_right .x , bbox_b .top_right .x )
35+ y_start = max (bbox_a .top_right .y , bbox_b .top_right .y )
36+ y_end = min (bbox_a .bottom_left .y , bbox_b .bottom_left .y )
37+
38+ overlapping_text = []
39+ for y in range (y_start , y_end + 1 ):
40+ row_content = rows [y ]
41+ overlapping_text .append (row_content [x_start : x_end + 1 ])
42+ return "\n " .join (overlapping_text )
43+
44+
45+ def part_number_count (schematic , part = 1 ):
546 part_numbers = 0
647 rows = schematic .splitlines ()
748 width = len (rows [0 ])
849 new_rows = ["." * (width + 2 )] + [f".{ row } ." for row in rows ] + ["." * (width + 2 )]
9- # print('\n'.join(new_rows))
50+
51+ bounding_boxes = {}
1052 for row_nr , line in enumerate (new_rows ):
1153 matches = re .finditer (r"\d+" , line )
1254 for match in matches :
1355 number = int (match .group ())
1456 start = match .start ()
1557 end = match .end () # End position (exclusive)
16- # print(f"Number: {number}, Start: {start}, End: {end}, row nr {row_nr}")
1758
18- symbols = []
1959 position_before = start - 1
20- symbols .append (new_rows [row_nr ][position_before ])
2160 position_after = end
22- symbols .append (new_rows [row_nr ][position_after ])
2361 row_nr_above = row_nr - 1
24- symbols .extend (new_rows [row_nr_above ][position_before : position_after + 1 ])
2562 row_nr_below = row_nr + 1
63+
64+ symbols = []
65+ symbols .append (new_rows [row_nr ][position_before ])
66+ symbols .append (new_rows [row_nr ][position_after ])
67+ symbols .extend (new_rows [row_nr_above ][position_before : position_after + 1 ])
2668 symbols .extend (new_rows [row_nr_below ][position_before : position_after + 1 ])
27- # print(set(symbols))
69+
2870 if len (set (symbols )) > 1 :
2971 part_numbers += number
30- return part_numbers
72+
73+ if part == 2 :
74+ if "*" in symbols :
75+ bottom_left = Point (position_before , row_nr_below )
76+ top_right = Point (position_after , row_nr_above )
77+ bounding_boxes [number ] = BoundingBox (bottom_left , top_right )
78+
79+ if part == 1 :
80+ return part_numbers
81+ else :
82+ gear_ratio = 0
83+ for part_number_a , part_number_b in combinations (
84+ sorted (bounding_boxes .keys ()), 2
85+ ):
86+ bbox_a = bounding_boxes [part_number_a ]
87+ bbox_b = bounding_boxes [part_number_b ]
88+ if intersects (bbox_a , bbox_b ) and "*" in overlapping_content (
89+ bbox_a , bbox_b , new_rows
90+ ):
91+ gear_ratio += part_number_a * part_number_b
92+ return gear_ratio
3193
3294
3395def main (part : int = 1 ) -> int :
3496 with open ("2023/data/day03.txt" ) as f :
3597 schematic = f .read ()
36- return part_number_count (schematic )
98+ return part_number_count (schematic , part )
3799
38100
39101if __name__ == "__main__" :
@@ -51,3 +113,7 @@ def main(part: int = 1) -> int:
51113 assert part_number_count (schematic ) == 4361
52114
53115 print (main ())
116+
117+ assert part_number_count (schematic , part = 2 ) == 467835
118+
119+ print (main (part = 2 ))
0 commit comments