1
1
import re
2
+ from collections import namedtuple
3
+ from itertools import combinations
2
4
5
+ Point = namedtuple ("Point" , ["x" , "y" ])
6
+ BoundingBox = namedtuple ("BoundingBox" , ["bottom_left" , "top_right" ])
3
7
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 ):
5
46
part_numbers = 0
6
47
rows = schematic .splitlines ()
7
48
width = len (rows [0 ])
8
49
new_rows = ["." * (width + 2 )] + [f".{ row } ." for row in rows ] + ["." * (width + 2 )]
9
- # print('\n'.join(new_rows))
50
+
51
+ bounding_boxes = {}
10
52
for row_nr , line in enumerate (new_rows ):
11
53
matches = re .finditer (r"\d+" , line )
12
54
for match in matches :
13
55
number = int (match .group ())
14
56
start = match .start ()
15
57
end = match .end () # End position (exclusive)
16
- # print(f"Number: {number}, Start: {start}, End: {end}, row nr {row_nr}")
17
58
18
- symbols = []
19
59
position_before = start - 1
20
- symbols .append (new_rows [row_nr ][position_before ])
21
60
position_after = end
22
- symbols .append (new_rows [row_nr ][position_after ])
23
61
row_nr_above = row_nr - 1
24
- symbols .extend (new_rows [row_nr_above ][position_before : position_after + 1 ])
25
62
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 ])
26
68
symbols .extend (new_rows [row_nr_below ][position_before : position_after + 1 ])
27
- # print(set(symbols))
69
+
28
70
if len (set (symbols )) > 1 :
29
71
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
31
93
32
94
33
95
def main (part : int = 1 ) -> int :
34
96
with open ("2023/data/day03.txt" ) as f :
35
97
schematic = f .read ()
36
- return part_number_count (schematic )
98
+ return part_number_count (schematic , part )
37
99
38
100
39
101
if __name__ == "__main__" :
@@ -51,3 +113,7 @@ def main(part: int = 1) -> int:
51
113
assert part_number_count (schematic ) == 4361
52
114
53
115
print (main ())
116
+
117
+ assert part_number_count (schematic , part = 2 ) == 467835
118
+
119
+ print (main (part = 2 ))
0 commit comments