-
Notifications
You must be signed in to change notification settings - Fork 0
/
advent10.py
executable file
·204 lines (166 loc) · 5.87 KB
/
advent10.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#!/usr/bin/env python3
"""
https://adventofcode.com/2023/day/10
Usage:
cat advent10.input | ./advent10.py
"""
import sys
from dataclasses import dataclass
def main ():
input = sys.stdin.read().strip().split('\n')
pipes = Pipes(input)
# Part1:
steps = pipes.find_path()
print("Part 1:", steps)
# 6882
# Part2:
pipes.replace_start_with_pipe()
inner_positions = pipes.find_inner_positions()
print("Part 2:", inner_positions)
# 491
class Pipes:
def __init__ (self, input):
self.pipes = {} # Hash with all pipes
row = 0
for line in input:
row += 1
col = 0
for symbol in line:
col += 1
self.pipes[Pos(row, col)] = Pipe(symbol)
if symbol == "S":
self.start = Pos(row, col)
self.cols = col
self.rows = row
def find_path (self):
"""
From the start position, we follow the pipes on both directions.
If both paths reach the same field, we stop.
"""
connected = self._connected(self.start)
self.path1 = [self.start, connected[0]]
self.path2 = [self.start, connected[1]]
while True:
# path 1
connected = self._connected(self.path1[-1])
come_from = self.path1[-2]
for pos in connected:
if pos != come_from:
self.path1.append(pos)
self.pipes[pos].is_in_path = True
# path 2
connected = self._connected(self.path2[-1])
come_from = self.path2[-2]
for pos in connected:
if pos != come_from:
self.path2.append(pos)
self.pipes[pos].is_in_path = True
# stop if both paths reach the same position:
if self.path1[-1] == self.path2[-1]:
break
return len(self.path1) - 1
def _connected (self, pos):
"""
Returns a list with the two positions that are connected to the given position/pipe.
"""
pipe = self.pipes[pos]
pipe.is_in_path = True
connected = list()
if pipe.north() and self.pipes[pos.north()].south():
connected.append(pos.north())
if pipe.east() and self.pipes[pos.east()].west():
connected.append(pos.east())
if pipe.south() and self.pipes[pos.south()].north():
connected.append(pos.south())
if pipe.west() and self.pipes[pos.west()].east():
connected.append(pos.west())
if len(connected) != 2:
# To find a matching pipe for the start position, we raise an exception if there are not exactly two connections:
raise Exception("Pipe at", pos, "has", len(connected), "connections")
return(connected)
def replace_start_with_pipe (self):
"""
Test each possible pipe symbol at the start position and replace the start position with
the pipe that connects to the rest of the pipes.
"""
for symbol in "|-LJ7F":
self.pipes[self.start] = Pipe(symbol, True)
try:
self._connected(self.start)
break
except:
pass
def find_inner_positions (self):
"""
Every time we cross the line, the status (boolean "inner_area") changes, whether we are
inside or outside the figure.
But you have to be careful whether you really cross the line or just touch it!
"""
for row in range(1, self.rows+1):
inner_area = False
path_comes_from = None
for col in range(1, self.cols+1):
pipe = self.pipes[Pos(row, col)]
if pipe.is_in_path:
if not pipe.west() and not pipe.east():
inner_area = not inner_area
elif not pipe.west():
if pipe.south():
path_comes_from = "south"
if pipe.north():
path_comes_from = "north"
elif not pipe.east():
if pipe.south() and path_comes_from == "north":
inner_area = not inner_area
if pipe.north() and path_comes_from == "south":
inner_area = not inner_area
else:
pipe.inner_area = inner_area
inner_positions = 0
for pos in self.pipes:
if self.pipes[pos].inner_area:
inner_positions += 1
return inner_positions
def __str__ (self):
s = ""
for row in range(1, self.rows+1):
for col in range(1, self.cols+1):
pipe = self.pipes[Pos(row, col)]
if pipe.is_in_path:
s += pipe.symbol
else:
if pipe.inner_area:
s += "+"
else:
s += pipe.symbol
s += "\n"
return s
@dataclass
class Pos:
row: int
col: int
def north (self):
return Pos(self.row-1, self.col)
def south (self):
return Pos(self.row+1, self.col)
def east (self):
return Pos(self.row, self.col+1)
def west (self):
return Pos(self.row, self.col-1)
def __hash__(self):
return hash((self.row, self.col))
@dataclass
class Pipe:
symbol: str
is_in_path: bool = False
inner_area: bool = False
def north (self):
return self.symbol in ["|", "L", "J", "S"]
def south (self):
return self.symbol in ["|", "7", "F", "S"]
def east (self):
return self.symbol in ["-", "F", "L", "S"]
def west (self):
return self.symbol in ["-", "7", "J", "S"]
if __name__ == '__main__':
main()