diff options
Diffstat (limited to 'day16/main.go')
-rw-r--r-- | day16/main.go | 138 |
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 +} |