-
Notifications
You must be signed in to change notification settings - Fork 0
/
solution.py
142 lines (114 loc) · 3.48 KB
/
solution.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
def read_input(inpf):
result = []
with open(inpf) as f:
for line in f:
line = line.strip().split('-')
result.append((line[0].strip(), line[1].strip()))
return result
class V:
def __init__(self, name):
self.name = name
self.is_upper = name == name.upper()
self.edges = set()
def outgoing(self):
out = set()
for e in self.edges:
for v in (e.v1, e.v2):
if v != self:
# if v.is_upper:
# for vv in v.outgoing():
# if vv != self:
# out.add(vv)
# else:
# out.add(v)
out.add(v)
return out
def __str__(self):
return self.name
def __repr__(self):
return self.name
class E:
def __init__(self, v1, v2, via_upper=False):
self.v1 = v1
self.v2 = v2
self.via_upper = via_upper
class G:
def __init__(self):
self.vertices = {}
self.edges = {}
def vertex(self, name):
v = self.vertices.get(name)
if not v:
v = V(name)
self.vertices[name] = v
return v
def edge(self, v1, v2, via_upper=False):
k1 = '{}-{}'.format(v1.name, v2.name)
k2 = '{}-{}'.format(v2.name, v1.name)
e = self.edges.get(k1) or self.edges.get(k2)
if not e:
e = E(v1, v2, via_upper)
self.edges[k1] = e
self.edges[k2] = e
v1.edges.add(e)
v2.edges.add(e)
return e
def part1(conns):
graph = G()
for a, b in conns:
graph.vertex(a)
graph.vertex(b)
for a, b in conns:
va = graph.vertex(a)
vb = graph.vertex(b)
graph.edge(va, vb)
start = graph.vertex('start')
end = graph.vertex('end')
def reach(v, end, path):
if v == end:
return 1
count = 0
for vn in v.outgoing():
if vn in path:
if not vn.is_upper:
continue
count += reach(vn, end, path + [v])
return count
return reach(start, end, [])
def part2(conns):
graph = G()
for a, b in conns:
graph.vertex(a)
graph.vertex(b)
for a, b in conns:
va = graph.vertex(a)
vb = graph.vertex(b)
graph.edge(va, vb)
start = graph.vertex('start')
end = graph.vertex('end')
small_caves = filter(lambda v: not v.is_upper, graph.vertices.values())
paths = set()
def reach(v, end, small_cave, path):
if v == end:
p = ','.join([a.name for a in path + [end]])
# print(p)
paths.add(p)
return
for vn in v.outgoing():
if vn in path:
if not vn.is_upper:
# count how many times this cave has been visited already
visited = len(list(filter(lambda c: c == vn, path)))
if vn == start:
continue
if vn == small_cave:
if visited == 2:
continue
else:
continue
reach(vn, end, small_cave, path + [v])
for sm in small_caves:
reach(start, end, sm, [])
return len(paths)
print('Part 1: ', part1(read_input('input')))
print('Part 2: ', part2(read_input('input')))