|
@@ -0,0 +1,135 @@
|
|
1
|
+import deques, sequtils, sets, strutils, tables
|
|
2
|
+
|
|
3
|
+type
|
|
4
|
+ Point = tuple[x,y: int]
|
|
5
|
+
|
|
6
|
+ Direction = enum
|
|
7
|
+ N, E, S, W
|
|
8
|
+
|
|
9
|
+ Sequence = ref object
|
|
10
|
+ forks: bool
|
|
11
|
+ nodes: seq[Sequence]
|
|
12
|
+ content: seq[Direction]
|
|
13
|
+
|
|
14
|
+proc `+` (a, b: Point): Point =
|
|
15
|
+ result.x = a.x + b.x
|
|
16
|
+ result.y = a.y + b.y
|
|
17
|
+
|
|
18
|
+# Parses the regular expression into a 'Sequence' data structure, by pushing
|
|
19
|
+# sequences onto a stack each time a nested expression is encountered.
|
|
20
|
+#
|
|
21
|
+# Three types of sequences are found:
|
|
22
|
+# 1) "Content-only", e.g. NNNEEE
|
|
23
|
+# 2) "Forks", e.g. (subsequence|subsequence|subsequence)
|
|
24
|
+# 3) "Concatenations", e.g. NN(subsequence)EE
|
|
25
|
+#
|
|
26
|
+func process(regex: string): Sequence =
|
|
27
|
+ var
|
|
28
|
+ stack = initDeque[Sequence]()
|
|
29
|
+ span: seq[Direction]
|
|
30
|
+ result = new(Sequence)
|
|
31
|
+ stack.addLast(result)
|
|
32
|
+
|
|
33
|
+ for c in regex:
|
|
34
|
+ if c == '^':
|
|
35
|
+ continue
|
|
36
|
+
|
|
37
|
+ if c in ['(', ')', '|', '$']:
|
|
38
|
+ if span.len > 0:
|
|
39
|
+ # If we've got bare directions, add them as a content element.
|
|
40
|
+ var newSeq = new(Sequence)
|
|
41
|
+ newSeq.content = span
|
|
42
|
+ stack.peekLast.nodes.add(newSeq)
|
|
43
|
+ span.setLen(0)
|
|
44
|
+ if c == '(':
|
|
45
|
+ # To start a new fork block we add two sequences: one that will
|
|
46
|
+ # contain all the forked elements, and the first child to hold
|
|
47
|
+ # the content of the first fork.
|
|
48
|
+ var forkSeq = new(Sequence)
|
|
49
|
+ forkSeq.forks = true
|
|
50
|
+ stack.addLast(forkSeq)
|
|
51
|
+ stack.addLast(new(Sequence))
|
|
52
|
+ elif c == '|':
|
|
53
|
+ # Pop the previous child off and add it to the parent fork block,
|
|
54
|
+ # then add a new one for the next branch.
|
|
55
|
+ var child = stack.popLast
|
|
56
|
+ stack.peekLast.nodes.add(child)
|
|
57
|
+ stack.addLast(new(Sequence))
|
|
58
|
+ elif c == ')':
|
|
59
|
+ # Pop both the child and the parent fork block off the stack.
|
|
60
|
+ var child = stack.popLast
|
|
61
|
+ stack.peekLast.nodes.add(child)
|
|
62
|
+ var forkSeq = stack.popLast
|
|
63
|
+ stack.peekLast.nodes.add(forkSeq)
|
|
64
|
+ else:
|
|
65
|
+ span.add(parseEnum[Direction]($c))
|
|
66
|
+
|
|
67
|
+ stack.popLast
|
|
68
|
+
|
|
69
|
+let
|
|
70
|
+ directions = {
|
|
71
|
+ N: ( 0, 1),
|
|
72
|
+ E: ( 1, 0),
|
|
73
|
+ S: ( 0, -1),
|
|
74
|
+ W: (-1, 0),
|
|
75
|
+ }.toTable
|
|
76
|
+ opposites = { N: S, E: W, S: N, W: E, }.toTable
|
|
77
|
+ input = readFile("data/20.txt").strip
|
|
78
|
+
|
|
79
|
+proc addDirection(table: var Table[Point, HashSet[Direction]], point: Point, direction: Direction) =
|
|
80
|
+ if not table.hasKey(point):
|
|
81
|
+ table[point] = initSet[Direction]()
|
|
82
|
+ table[point].incl(direction)
|
|
83
|
+
|
|
84
|
+proc addDirections(table: var Table[Point, HashSet[Direction]], point: Point, direction: Direction): Point =
|
|
85
|
+ let otherPoint = point + directions[direction]
|
|
86
|
+ table.addDirection(point, direction)
|
|
87
|
+ table.addDirection(otherPoint, opposites[direction])
|
|
88
|
+ otherPoint
|
|
89
|
+
|
|
90
|
+# Recurses through the given sequence and builds up a map of which points are
|
|
91
|
+# traversable in which directions.
|
|
92
|
+#
|
|
93
|
+# The points parameter tracks which points could have been reached by the
|
|
94
|
+# sequence thus far. The current sequence must be evaluated for all of those
|
|
95
|
+# points. Initially this is just {(0,0)} but every time a fork is encountered
|
|
96
|
+# the number of points will expand.
|
|
97
|
+proc map(sequence: Sequence, grid: var Table[Point, HashSet[Direction]], points: HashSet[Point]): HashSet[Point] =
|
|
98
|
+ if sequence.content.len > 0:
|
|
99
|
+ result = initSet[Point]()
|
|
100
|
+ for point in points:
|
|
101
|
+ var newPoint = point
|
|
102
|
+ for d in sequence.content:
|
|
103
|
+ newPoint = grid.addDirections(newPoint, d)
|
|
104
|
+ result.incl(newPoint)
|
|
105
|
+ elif sequence.forks:
|
|
106
|
+ result = initSet[Point]()
|
|
107
|
+ for child in sequence.nodes:
|
|
108
|
+ result.incl(child.map(grid, points))
|
|
109
|
+ else:
|
|
110
|
+ result = points
|
|
111
|
+ for child in sequence.nodes:
|
|
112
|
+ result = child.map(grid, result)
|
|
113
|
+
|
|
114
|
+# Performs a breadth-first search of the navigable area and assigns a
|
|
115
|
+# distance to each point.
|
|
116
|
+proc score(dirs: Table[Point, HashSet[Direction]]): Table[Point, int] =
|
|
117
|
+ result = initTable[Point, int]()
|
|
118
|
+ var stack = initDeque[tuple[pos: Point, length: int]]()
|
|
119
|
+ stack.addLast(((0, 0), 0))
|
|
120
|
+ while stack.len > 0:
|
|
121
|
+ let step = stack.popFirst
|
|
122
|
+ if not result.hasKey(step.pos) or result[step.pos] > step.length:
|
|
123
|
+ result[step.pos] = step.length
|
|
124
|
+ for direction in dirs[step.pos]:
|
|
125
|
+ stack.addLast((step.pos + directions[direction], step.length + 1))
|
|
126
|
+
|
|
127
|
+proc distances(input: string): seq[int] =
|
|
128
|
+ var grid = initTable[Point, HashSet[Direction]]()
|
|
129
|
+ let points = toSet([(0, 0)])
|
|
130
|
+ discard input.process.map(grid, points)
|
|
131
|
+ toSeq(grid.score.values)
|
|
132
|
+
|
|
133
|
+let distanceValues = input.distances
|
|
134
|
+echo distanceValues.max
|
|
135
|
+echo distanceValues.filter(proc(i: int): bool = i >= 1000).len
|