-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
177 lines (149 loc) · 4.26 KB
/
main.go
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package main
import (
"fmt"
"strconv"
pq "github.com/emirpasic/gods/queues/priorityqueue"
aoc "github.com/shraddhaag/aoc/library"
)
func main() {
input := aoc.ReadFileLineByLine("input.txt")
matrix := createMatrix(input)
fmt.Println("answer for part 1: ", dijktras(matrix, 0, 3))
fmt.Println("answer for part 2: ", dijktras(matrix, 4, 10))
}
func createMatrix(input []string) [][]int {
output := make([][]int, len(input))
for i, line := range input {
output[i] = make([]int, len(line))
for j, char := range line {
num, _ := strconv.ParseInt(string(char), 10, 64)
output[i][j] = int(num)
}
}
return output
}
type step struct {
x int
y int
lastDir direction
count int
}
type queueNode struct {
step
heatLoss int
}
type direction string
const (
up direction = "up"
down direction = "down"
left direction = "left"
right direction = "right"
)
func dijktras(matrix [][]int, minX, maxX int) int {
// priority queue is used to store the (state, heatloss) for future
// possible direction
// it starts with the first two possible directions enqueued and as
// the least cost node is dequeued, we enqueue all possible states
// that can be reached from the current node.
priorityQueue := pq.NewWith(func(a, b interface{}) int {
return a.(queueNode).heatLoss - b.(queueNode).heatLoss
})
// enqueue two possibility of starting points, going left and going down
// note: this is really subtle and not made explicity clear in the problem
// statement, but the starting point (0,0) does not count towards the direction count.
// this means, if we start at (0,0) going right, we can travel upto (0, max), not (0, max-1).
priorityQueue.Enqueue(queueNode{step{0, 1, right, 1}, matrix[0][1]})
priorityQueue.Enqueue(queueNode{step{1, 0, down, 1}, matrix[1][0]})
// visited is a cache used to prevent loops of states
visited := make(map[step]int)
for !priorityQueue.Empty() {
element, _ := priorityQueue.Dequeue()
// priority queue always gives the least heatloss node present in the queue
currentNode := element.(queueNode)
if _, ok := visited[currentNode.step]; ok {
continue
}
// if the final node is reached, exit
// the first occurance is the answer because we are using a priority queue
// and it will dequeue the least heatloss state first.
if currentNode.x == len(matrix)-1 && currentNode.y == len(matrix[0])-1 {
if currentNode.count < minX {
continue
}
return currentNode.heatLoss
}
// find all possible traversals from the current node and insert them in the queue
dirs := fetchPossibleDirections(currentNode, matrix, minX, maxX)
possibleNextNodes := getNodesFromDirection(dirs, currentNode, matrix)
for _, p := range possibleNextNodes {
if _, ok := visited[p.step]; ok {
continue
}
priorityQueue.Enqueue(p)
}
visited[currentNode.step] = currentNode.heatLoss
}
return -1
}
type point struct {
x int
y int
}
var positionMap = map[direction]point{
up: {-1, 0},
down: {1, 0},
left: {0, -1},
right: {0, 1},
}
func isValid(p point, input [][]int) bool {
if p.x < 0 || p.x > len(input)-1 || p.y < 0 || p.y > len(input[0])-1 {
return false
}
return true
}
func fetchPossibleDirections(node queueNode, matrix [][]int, min, max int) []direction {
output := []direction{}
switch node.lastDir {
case up, down:
if node.count < max {
output = append(output, node.lastDir)
}
if node.count >= min {
output = append(output, left)
output = append(output, right)
}
case left, right:
if node.count < max {
output = append(output, node.lastDir)
}
if node.count >= min {
output = append(output, up)
output = append(output, down)
}
}
return output
}
func getNodesFromDirection(dirs []direction, startNode queueNode, matrix [][]int) []queueNode {
nodes := []queueNode{}
for _, dir := range dirs {
addPoint := positionMap[dir]
newPoint := point{startNode.x + addPoint.x, startNode.y + addPoint.y}
if !isValid(newPoint, matrix) {
continue
}
count := 1
if startNode.lastDir == dir {
count = startNode.count + 1
}
nodes = append(nodes, queueNode{
step{
x: startNode.x + addPoint.x,
y: startNode.y + addPoint.y,
lastDir: dir,
count: count,
},
startNode.heatLoss + matrix[newPoint.x][newPoint.y],
})
}
return nodes
}