-
Notifications
You must be signed in to change notification settings - Fork 0
/
10.py
149 lines (120 loc) · 3.14 KB
/
10.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
import sys
from itertools import pairwise
def neighbors(x, y, shape):
match shape:
case "|":
yield from [(x, y - 1), (x, y + 1)]
case "-":
yield from [(x + 1, y), (x - 1, y)]
case "L":
yield from [(x, y - 1), (x + 1, y)]
case "J":
yield from [(x - 1, y), (x, y - 1)]
case "7":
yield from [(x - 1, y), (x, y + 1)]
case "F":
yield from [(x + 1, y), (x, y + 1)]
case _:
raise ValueError(shape)
def parse_input(puzzle_input):
tiles, start = {}, None
for y, row in enumerate(puzzle_input.splitlines()):
for x, c in enumerate(row):
if c == "S":
start = (x, y)
tiles[(x, y)] = c
assert start is not None
# determine start pipe shape
x, y = start
north, east, south, west = map(
tiles.get, [(x, y - 1), (x + 1, y), (x, y + 1), (x - 1, y)]
)
possible = set("|-LJ7F")
if north and north in "|7F":
possible -= set("-7F")
if east and east in "-J7":
possible -= set("|J7")
if south and south in "|LJ":
possible -= set("-LJ")
if west and west in "-LF":
possible -= set("|LF")
assert len(possible) == 1
shape = possible.pop()
# walk the loop
path = [start]
prev, curr = start, next(neighbors(*start, shape))
while curr != start:
path.append(curr)
prev, curr = curr, (set(neighbors(*curr, tiles[curr])) - {prev}).pop()
return (path, tiles)
def part_one(path, tiles):
return len(path) // 2
def part_two(path, tiles):
v = [t for t in path if tiles[t] not in "|-"]
# Shoelace formula: <https://en.wikipedia.org/wiki/Shoelace_formula>
area = abs(sum(a * d - b * c for (a, c), (b, d) in pairwise(v + v[:1]))) // 2
# Pick's theorem: <https://en.wikipedia.org/wiki/Pick%27s_theorem>
return area - len(path) // 2 + 1
class Test:
example1 = """\
.....
.S-7.
.|.|.
.L-J.
.....
"""
example2 = """\
..F7.
.FJ|.
SJ.L7
|F--J
LJ...
"""
example3 = """\
...........
.S-------7.
.|F-----7|.
.||.....||.
.||.....||.
.|L-7.F-J|.
.|..|.|..|.
.L--J.L--J.
...........
"""
example4 = """\
.F----7F7F7F7F-7....
.|F--7||||||||FJ....
.||.FJ||||||||L7....
FJL7L7LJLJ||LJ.L-7..
L--J.L7...LJS7F-7L7.
....F-J..F7FJ|L7L7L7
....L7.F7||L7|.L7L7|
.....|FJLJ|FJ|F7|.LJ
....FJL-7.||.||||...
....L---J.LJ.LJLJ...
"""
example5 = """\
FF7FSF7F7F7F7F7F---7
L|LJ||||||||||||F--J
FL-7LJLJ||||||LJL-77
F--JF--7||LJLJ7F7FJ-
L---JF-JLJ.||-FJLJJ7
|F|F-JF---7F7-L7L|7|
|FFJF7L7F-JF7|JL---7
7-L-JL7||F7|L7F-7F7|
L.L7LFJ|||||FJL7||LJ
L7JLJL-JLJLJL--JLJ.L
"""
def test_one(self):
assert part_one(*parse_input(self.example1)) == 4
assert part_one(*parse_input(self.example2)) == 8
def test_two(self):
assert part_two(*parse_input(self.example3)) == 4
assert part_two(*parse_input(self.example4)) == 8
assert part_two(*parse_input(self.example5)) == 10
def main():
puzzle = parse_input(sys.stdin.read())
print("part 1:", part_one(*puzzle))
print("part 2:", part_two(*puzzle))
if __name__ == "__main__":
sys.exit(main())