This repository was archived by the owner on Jan 2, 2023. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathday22.nim
107 lines (87 loc) · 3.38 KB
/
day22.nim
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
import sequtils, strutils, tables
type
Point = tuple[x,y: int]
Tool = enum tTorch, tClimbing, tNeither
Node = tuple[pos: Point, tool: Tool]
iterator moves(point: Point): Point =
let (x, y) = point
if y > 0: yield (x, y - 1)
if x > 0: yield (x - 1, y)
yield (x, y + 1)
yield (x + 1, y)
let
input = readFile("data/22.txt").splitLines
depth = input[0].strip.split(' ')[1].parseInt
targetParts = input[1].strip.split(' ')[1].split(',').map(parseInt)
target: Point = (targetParts[0], targetParts[1])
targetNode: Node = (target, tTorch)
tools = [[tClimbing, tTorch], [tClimbing, tNeither], [tTorch, tNeither]]
var
erosions = initTable[Point, int]()
dangerSum = 0
# Add arbitrary extension to allow for a bit of overshooting.
for y in 0..target.y + 75:
for x in 0..target.x + 75:
let
geoindex = if (x == 0 and y == 0) or (x, y) == target:
0
elif y == 0:
x * 16807
elif x == 0:
y * 48271
else:
erosions[(x-1, y)] * erosions[(x, y-1)]
erosionlevel = (geoindex + depth) mod 20183
erosions[(x, y)] = erosionlevel
if x <= target.x and y <= target.y:
dangerSum += erosionlevel mod 3
# Custom stack of pending steps, kept in order from smallest to largest.
# Insertion is O(n), removing the smallest is O(1). Performs approximately a
# billionty times faster than I managed using a Seq/Deque/Table/CountingTable.
type
StackStep = ref object
node: Node
distance: int
next: StackStep
Stack = object
head: StackStep
proc insert(stack: var Stack, node: Node, distance: int) =
var newNode = new(StackStep)
newNode.node = node
newNode.distance = distance
if stack.head == nil:
stack.head = newNode
else:
var target = stack.head
while target.next != nil and target.next.distance < distance:
target = target.next
newNode.next = target.next
target.next = newNode
proc popSmallest(stack: var Stack): tuple[node: Node, distance: int] =
result = (stack.head.node, stack.head.distance)
stack.head = stack.head.next
var
distances = initTable[Node, int]()
stack: Stack
stack.insert(((0, 0), tTorch), 0)
while not distances.hasKey(targetNode):
let (node, distance) = stack.popSmallest
# We can have duplicate steps in our stack, but if we've already
# put the distance then that route was necessarily shorter. Just
# skip it.
if distances.hasKey(node):
continue
distances[node] = distance
# At each node we can switch tools once, with a cost of 7 minutes
for tool in tools[erosions[node.pos] mod 3]:
if tool != node.tool and not distances.hasKey((node.pos, tool)):
stack.insert(((node.pos, tool)), distance + 7)
# Up to four possible moves from the current node, depending on
# terrain and tools.
for newPos in node.pos.moves:
if erosions.hasKey(newPos) and
node.tool in tools[erosions[newPos] mod 3] and
not distances.hasKey((newPos, node.tool)):
stack.insert(((newPos, node.tool)), distance + 1)
echo dangerSum
echo distances[targetNode]