Files
adventofcode/2024/gareth/day20/day20.go
Gareth 225d74acf9 Day20
2025-01-14 15:21:34 +00:00

192 lines
4.3 KiB
Go

package day20
import (
"container/heap"
"fmt"
"math"
"strings"
)
var directions = [][]int{
{0, 1}, // Right
{1, 0}, // Down
{0, -1}, // Left
{-1, 0}, // Up
}
type State struct {
row, col, dir, score int
path []string
//Would rather use tuple as key but cant get to work
costMap map[[2]int]int
}
type PriorityQueue []State
func Part1(input string) int {
maze := parseInput(input)
_, path, costMap := solveMaze(maze)
cheats := findCheats(path, costMap)
return cheats
}
func Part2(input string) int {
maze := parseInput(input)
_, path, costMap := solveMaze(maze)
cheats := findCheatsPart2(path, costMap)
return cheats
}
func parseInput(input string) []string {
parts := strings.Split(input, "\n")
maze := []string{}
maze = append(maze, parts...)
return maze
}
func (pq PriorityQueue) Len() int { return len(pq) }
func (pq PriorityQueue) Less(i, j int) bool {
return pq[i].score < pq[j].score
}
func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] }
func (pq *PriorityQueue) Push(x interface{}) {
*pq = append(*pq, x.(State))
}
func (pq *PriorityQueue) Pop() interface{} {
old := *pq
n := len(old)
item := old[n-1]
*pq = old[0 : n-1]
return item
}
func solveMaze(maze []string) (int, []string, map[[2]int]int) {
rows := len(maze)
cols := len(maze[0])
// Locate start and end points
var startRow, startCol, endRow, endCol int
for i := 0; i < rows; i++ {
for j := 0; j < cols; j++ {
if maze[i][j] == 'S' {
startRow, startCol = i, j
} else if maze[i][j] == 'E' {
endRow, endCol = i, j
}
}
}
// Priority queue and visited set
pq := &PriorityQueue{}
heap.Init(pq)
startPath := []string{fmt.Sprintf("(%d,%d)", startRow, startCol)}
startCostMap := map[[2]int]int{
{startRow, startCol}: 0,
}
heap.Push(pq, State{startRow, startCol, 0, 0, startPath, startCostMap})
visited := make(map[[3]int]bool)
for pq.Len() > 0 {
cur := heap.Pop(pq).(State)
// If reached the end point, return the score and path
if cur.row == endRow && cur.col == endCol {
return cur.score, cur.path, cur.costMap
}
// Mark as visited
key := [3]int{cur.row, cur.col, cur.dir}
if visited[key] {
continue
}
visited[key] = true
// Explore moves
for i := 0; i < 4; i++ {
// Compute new direction
newDir := (cur.dir + i) % 4
// Compute new position
newRow := cur.row + directions[newDir][0]
newCol := cur.col + directions[newDir][1]
// Check bounds and wall
if newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols && maze[newRow][newCol] != '#' {
newCost := cur.score + 1
if oldCost, exists := cur.costMap[[2]int{newRow, newCol}]; !exists || newCost < oldCost {
// Update cost map
newCostMap := make(map[[2]int]int)
for k, v := range cur.costMap {
newCostMap[k] = v
}
newCostMap[[2]int{newRow, newCol}] = newCost
// Update path
newPath := append([]string{}, cur.path...)
newPath = append(newPath, fmt.Sprintf("(%d,%d)", newRow, newCol))
heap.Push(pq, State{
newRow,
newCol,
newDir,
newCost,
newPath,
newCostMap,
})
}
}
}
}
return -1, nil, nil // No path found
}
func findCheats(path []string, costLookup map[[2]int]int) int {
totalCheats := 0
for index, position := range path {
var row, col int
//Should be a better way to do this
fmt.Sscanf(position, "(%d,%d)", &row, &col)
for _, dir := range directions {
newRow := row + (dir[0] * 2)
newCol := col + (dir[1] * 2)
if cost, exists := costLookup[[2]int{newRow, newCol}]; exists {
if cost > index && (cost-index-2) >= 100 {
totalCheats++
}
}
}
}
return totalCheats
}
func findCheatsPart2(path []string, costLookup map[[2]int]int) int {
totalCheats := 0
for index, position := range path {
var row, col int
//Should be a better way to do this
fmt.Sscanf(position, "(%d,%d)", &row, &col)
for x1 := row - 20; x1 <= row+20; x1++ {
for y1 := col - 20 + int(math.Abs(float64(row-x1))); y1 <= col+20-int(math.Abs(float64(row-x1))); y1++ {
if cost, exists := costLookup[[2]int{x1, y1}]; exists && cost != -1 {
diff := cost - index - int(math.Abs(float64(row-x1))) - int(math.Abs(float64(col-y1)))
if cost > index && diff >= 100 {
totalCheats++
}
}
}
}
}
return totalCheats
}