Browse Source

Day 20.

Phew.
master
Chris Smith 5 years ago
parent
commit
a1e4380c1c
3 changed files with 138 additions and 0 deletions
  1. 2
    0
      answers/20.txt
  2. 1
    0
      data/20.txt
  3. 135
    0
      day20.nim

+ 2
- 0
answers/20.txt View File

@@ -0,0 +1,2 @@
1
+3469
2
+8780

+ 1
- 0
data/20.txt
File diff suppressed because it is too large
View File


+ 135
- 0
day20.nim View File

@@ -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