/
20.py
112 lines (91 loc) · 3 KB
/
20.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
#!/usr/bin/env python3
#
# thehungryturnip@gmail.com
#
# Day 20 of the Advent of Code 2018
#
import re
import sys
from collections import namedtuple
from enum import Enum, auto
from networkx import Graph, algorithms
class Direction(Enum):
EAST = 'E'
SOUTH = 'S'
WEST = 'W'
NORTH = 'N'
class Coord(namedtuple('Coord', ['x', 'y'])):
def delta(self, dir_):
delta = DIRECTION_DELTAS[dir_]
return Coord(self.x + delta.x, self.y + delta.y)
def __str__(self):
return f'({self.x},{self.y})'
def __repr__(self):
return self.__str__()
DIRECTION_DELTAS = {
Direction.EAST: Coord(1, 0),
Direction.SOUTH: Coord(0, 1),
Direction.WEST: Coord(-1, 0),
Direction.NORTH: Coord(0, -1),
}
class Instruction(Enum):
START = '^'
END = '$'
STEP = 'ESWN'
START_BRANCHING = '('
NEW_BRANCH = '|'
END_BRANCHING = ')'
class Split:
def __init__(self, branches):
self.prev = branches
self.next = set()
class Map(Graph):
DEFSTR_REGEX = '\^.*\$'
def __init__(self, defstr):
super().__init__()
self.defstring = defstr
self.build_graph()
self.lengths = algorithms.shortest_path_length(self, Coord(0, 0))
def build_graph(self):
for c in self.defstring:
if c is Instruction.START.value:
self.clear()
branches = set([Coord(0, 0)])
splits = []
elif c in Instruction.STEP.value:
new_branches = set()
for curr_node in branches:
next_node = curr_node.delta(Direction(c))
self.add_edge(curr_node, next_node)
new_branches.add(next_node)
branches = new_branches
elif c in Instruction.START_BRANCHING.value:
splits.append(Split(branches))
elif c in Instruction.NEW_BRANCH.value:
splits[-1].next = splits[-1].next.union(branches)
branches = splits[-1].prev
elif c in Instruction.END_BRANCHING.value:
branches = branches.union(splits.pop().next)
def furthest_distance(self):
return max(self.lengths.values())
def distance_over(self, length):
return len([v for v in self.lengths.values() if v >= 1000])
def __str__(self):
return f'nodes:{len(self.nodes)} edges:{len(self.edges)}\n{self.edges}'
if __name__ == '__main__':
COMMENT_CHAR = '#'
filename = sys.argv[1]
print(f'filename: {filename}')
defstrs = []
with open(filename, 'r') as f:
for l in [l.strip() for l in f.readlines()]:
if not l[0] == COMMENT_CHAR:
defstrs += re.findall(Map.DEFSTR_REGEX, l)
maps = []
for d in defstrs:
m = Map(d)
maps.append(m)
for m in maps:
print(m.defstring)
print(f'[20a] Furthest room is {m.furthest_distance()} away.')
print(f'[20b] {m.distance_over(1000)} rooms 1000 or more doors away.')