-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathday12.py
138 lines (115 loc) · 3.89 KB
/
day12.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import sys
def read_input():
if len(sys.argv) == 2:
with open(sys.argv[1]) as f:
return f.read().strip()
return sys.stdin.read().strip()
def parse_data(input):
data = {}
for r, line in enumerate(input.splitlines()):
for c, cell in enumerate(line):
data[(r, c)] = cell
return data
def contiguous_regions(data: dict[tuple[int, int], str]):
regions = [] # A list of sets of coordinates
visited = set()
for coord, cell in data.items():
if coord in visited:
continue
visited.add(coord)
# Explore adjacent cells to find a region of identical cells
region = {coord}
stack = [coord]
while stack:
r, c = stack.pop()
for dr, dc in ((-1, 0), (1, 0), (0, -1), (0, 1)):
neighbor = (r + dr, c + dc)
if data.get(neighbor) == cell and neighbor not in region:
stack.append(neighbor)
region.add(neighbor)
visited.add(neighbor)
regions.append(region)
return regions
def score_pt1(region: set[tuple[int, int]]) -> int:
"""A score of a region is its area * its perimeter"""
# Could probably calculate this for each region as its discovered, but...
perimeter = 0
for r, c in region:
for dr, dc in ((-1, 0), (1, 0), (0, -1), (0, 1)):
if (r + dr, c + dc) not in region:
perimeter += 1
return len(region) * perimeter
def do_the_thing(score):
data = parse_data(read_input())
regions = contiguous_regions(data)
return sum(score(region) for region in regions)
def part1():
return do_the_thing(score_pt1)
def number_sides(region: set[tuple[int, int]]) -> int:
# we'll just iterate horizontally and vertically and count the unmber of unique runs.
rows = set()
cols = set()
for r, c in region:
rows.add(r)
cols.add(c)
minr, minc = min(rows), min(cols)
maxr, maxc = max(rows), max(cols)
h_sides = 0
for r in range(minr, maxr + 1):
a_run, b_run = False, False
for c in range(minc, maxc + 1):
if (r, c) not in region:
a_run, b_run = False, False
continue
if (r - 1, c) not in region:
if not a_run:
h_sides += 1
a_run = True
else:
a_run = False
if (r + 1, c) not in region:
if not b_run:
h_sides += 1
b_run = True
else:
b_run = False
v_sides = 0
for c in range(minc, maxc + 1):
l_run, r_run = False, False
for r in range(minr, maxr + 1):
if (r, c) not in region:
l_run, r_run = False, False
continue
if (r, c - 1) not in region:
if not l_run:
v_sides += 1
l_run = True
else:
l_run = False
if (r, c + 1) not in region:
if not r_run:
v_sides += 1
r_run = True
else:
r_run = False
return h_sides + v_sides
def debug_print(region, data):
maxr, maxc = 0, 0
maxr, maxc = list((max(r, maxr), max(c, maxc)) for r, c in data)[-1]
for r in range(maxr + 1):
for c in range(maxc + 1):
if (r, c) in region:
print("\033[0;31m", end="")
print(data.get((r, c), " "), end="")
if (r, c) in region:
print("\033[0m", end="")
print()
def score_pt2(region: set[tuple[int, int]]) -> int:
"""A score of a region is its area * its number of sides"""
sides = number_sides(region)
area = len(region)
return area * sides
def part2():
return do_the_thing(score_pt2)
if __name__ == "__main__":
print(part2())