Browse Source

Day 22

master
Chris Smith 5 years ago
parent
commit
a57d3500df
3 changed files with 111 additions and 0 deletions
  1. 2
    0
      answers/22.txt
  2. 2
    0
      data/22.txt
  3. 107
    0
      day22.nim

+ 2
- 0
answers/22.txt View File

@@ -0,0 +1,2 @@
1
+10204
2
+1004

+ 2
- 0
data/22.txt View File

@@ -0,0 +1,2 @@
1
+depth: 7305
2
+target: 13,734

+ 107
- 0
day22.nim View File

@@ -0,0 +1,107 @@
1
+import sequtils, strutils, tables
2
+
3
+type
4
+    Point = tuple[x,y: int]
5
+    Tool = enum tTorch, tClimbing, tNeither
6
+    Node = tuple[pos: Point, tool: Tool]
7
+
8
+iterator moves(point: Point): Point =
9
+    let (x, y) = point
10
+    
11
+    if y > 0: yield (x, y - 1)
12
+    if x > 0: yield (x - 1, y)
13
+    yield (x, y + 1)
14
+    yield (x + 1, y)
15
+
16
+let
17
+    input = readFile("data/22.txt").splitLines
18
+    depth = input[0].strip.split(' ')[1].parseInt
19
+    targetParts = input[1].strip.split(' ')[1].split(',').map(parseInt)
20
+    target: Point = (targetParts[0], targetParts[1])
21
+    targetNode: Node = (target, tTorch)
22
+    tools = [[tClimbing, tTorch], [tClimbing, tNeither], [tTorch, tNeither]]
23
+
24
+var
25
+    erosions = initTable[Point, int]()
26
+    dangerSum = 0
27
+
28
+# Add arbitrary extension to allow for a bit of overshooting.
29
+for y in 0..target.y + 20:
30
+    for x in 0..target.x + 20:
31
+        let
32
+            geoindex = if (x == 0 and y == 0) or (x, y) == target:
33
+                    0
34
+                elif y == 0:
35
+                    x * 16807
36
+                elif x == 0:
37
+                    y * 48271
38
+                else:
39
+                    erosions[(x-1, y)] * erosions[(x, y-1)]
40
+            erosionlevel = (geoindex + depth) mod 20183
41
+        
42
+        erosions[(x, y)] = erosionlevel
43
+        if x <= target.x and y <= target.y:
44
+            dangerSum += erosionlevel mod 3
45
+
46
+# Custom stack of pending steps, kept in order from smallest to largest.
47
+# Insertion is O(n), removing the smallest is O(1). Performs approximately a
48
+# billionty times faster than I managed using a Seq/Deque/Table/CountingTable.
49
+type
50
+    StackStep = ref object
51
+        node: Node
52
+        distance: int
53
+        next: StackStep
54
+    
55
+    Stack = object
56
+        head: StackStep
57
+
58
+proc insert(stack: var Stack, node: Node, distance: int) =
59
+    var newNode = new(StackStep)
60
+    newNode.node = node
61
+    newNode.distance = distance
62
+
63
+    if stack.head == nil:
64
+        stack.head = newNode
65
+    else:
66
+        var target = stack.head
67
+        while target.next != nil and target.next.distance < distance:
68
+            target = target.next
69
+        newNode.next = target.next
70
+        target.next = newNode
71
+
72
+proc popSmallest(stack: var Stack): tuple[node: Node, distance: int] =
73
+    result = (stack.head.node, stack.head.distance)
74
+    stack.head = stack.head.next
75
+
76
+var
77
+    distances = initTable[Node, int]()
78
+    stack: Stack
79
+
80
+stack.insert(((0, 0), tTorch), 0)
81
+
82
+while not distances.hasKey(targetNode):
83
+    let (node, distance) = stack.popSmallest
84
+
85
+    # We can have duplicate steps in our stack, but if we've already
86
+    # put the distance then that route was necessarily shorter. Just
87
+    # skip it.
88
+    if distances.hasKey(node):
89
+        continue
90
+    
91
+    distances[node] = distance
92
+
93
+    # At each node we can switch tools once, with a cost of 7 minutes
94
+    for tool in tools[erosions[node.pos] mod 3]:
95
+        if tool != node.tool and not distances.hasKey((node.pos, tool)):
96
+            stack.insert(((node.pos, tool)), distance + 7)
97
+
98
+    # Up to four possible moves from the current node, depending on
99
+    # terrain and tools.
100
+    for newPos in node.pos.moves:
101
+        if erosions.hasKey(newPos) and
102
+                node.tool in tools[erosions[newPos] mod 3] and
103
+                not distances.hasKey((newPos, node.tool)):
104
+            stack.insert(((newPos, node.tool)), distance + 1)
105
+
106
+echo dangerSum
107
+echo distances[targetNode]