summaryrefslogtreecommitdiff
path: root/day16/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'day16/main.go')
-rw-r--r--day16/main.go138
1 files changed, 138 insertions, 0 deletions
diff --git a/day16/main.go b/day16/main.go
new file mode 100644
index 0000000..fd8b346
--- /dev/null
+++ b/day16/main.go
@@ -0,0 +1,138 @@
+package main
+
+import (
+ "bufio"
+ "container/heap"
+ "fmt"
+ "math"
+ "os"
+ "strings"
+)
+
+func main() {
+ fmt.Println(7036, 45)
+ fmt.Println(part1And2(readData("example.txt")))
+ fmt.Println(11048, 64)
+ fmt.Println(part1And2(readData("example2.txt")))
+ fmt.Println(89460, 504)
+ fmt.Println(part1And2(readData("data.txt")))
+}
+
+type Position struct {
+ x, y int
+}
+
+type PositionDir struct {
+ x, y int
+ dir int
+}
+
+type Candidate struct {
+ x, y int
+ dir int
+ cost int
+ visited []PositionDir
+}
+
+type CandidateQueue struct {
+ elements []Candidate
+}
+
+func (queue *CandidateQueue) Len() int {
+ return len(queue.elements)
+}
+
+func (queue *CandidateQueue) Less(i, j int) bool {
+ return queue.elements[i].cost < queue.elements[j].cost
+}
+
+func (queue *CandidateQueue) Swap(i, j int) {
+ queue.elements[i], queue.elements[j] = queue.elements[j], queue.elements[i]
+}
+
+func (queue *CandidateQueue) Push(x any) {
+ queue.elements = append(queue.elements, x.(Candidate))
+}
+
+func (queue *CandidateQueue) AddElement(candidate Candidate) {
+ heap.Push(queue, candidate)
+}
+
+func (queue *CandidateQueue) Pop() any {
+ if len(queue.elements) == 0 {
+ return nil
+ } else {
+ elmt := queue.elements[len(queue.elements)-1]
+ queue.elements = queue.elements[:len(queue.elements)-1]
+ return elmt
+ }
+}
+
+func cloneVisited(v []PositionDir) []PositionDir {
+ clone := make([]PositionDir, len(v))
+ for i, p := range v {
+ clone[i] = p
+ }
+ return clone
+}
+
+var directions = [4][2]int{{1, 0}, {0, 1}, {-1, 0}, {0, -1}}
+
+// Originally I had implemented A* for part1. But because of part2 I modified it to be
+// dumber. I wish we had full requirements from the start so I wouldn't waste time...
+
+func part1And2(maze [][]byte) (scorePart1, scorePart2 int) {
+ endX, endY := len(maze[0])-2, 1
+ startX, startY := 1, len(maze)-2
+
+ queue := new(CandidateQueue)
+ heap.Push(queue, Candidate{startX, startY, 0, 0, []PositionDir{{startX, startY, 0}}})
+ minCost := math.MaxInt
+ visitedCost := make(map[PositionDir]int)
+ onOptimalPath := make(map[Position]bool)
+
+ for queue.Len() > 0 {
+ candidate := heap.Pop(queue).(Candidate)
+ posDir := PositionDir{candidate.x, candidate.y, candidate.dir}
+
+ alreadyVisitedCost, alreadyVisited := visitedCost[posDir]
+ if alreadyVisited && alreadyVisitedCost < candidate.cost {
+ continue
+ }
+ visitedCost[posDir] = candidate.cost
+
+ tryDirection := func(dir int, cost int) {
+ fwdX := candidate.x + directions[dir][0]
+ fwdY := candidate.y + directions[dir][1]
+ realCostFwd := candidate.cost + cost
+ if maze[fwdY][fwdX] != '#' && realCostFwd <= minCost {
+ if fwdX == endX && fwdY == endY && realCostFwd <= minCost {
+ for _, p := range candidate.visited {
+ onOptimalPath[Position{p.x, p.y}] = true
+ }
+ minCost = realCostFwd
+ } else {
+ visited := append(cloneVisited(candidate.visited), PositionDir{fwdX, fwdY, dir})
+ c := Candidate{fwdX, fwdY, dir, realCostFwd, visited}
+ heap.Push(queue, c)
+ }
+ }
+ }
+
+ tryDirection(candidate.dir, 1)
+ tryDirection((candidate.dir+1)%4, 1001)
+ tryDirection((candidate.dir+3)%4, 1001)
+ }
+
+ return minCost, len(onOptimalPath) + 1
+}
+
+func readData(fileName string) (maze [][]byte) {
+ fp, _ := os.Open(fileName)
+ scanner := bufio.NewScanner(fp)
+
+ for scanner.Scan() {
+ maze = append(maze, []byte(strings.TrimSpace(scanner.Text())))
+ }
+ return
+}