Day 18
This commit is contained in:
3450
day18/data.txt
Normal file
3450
day18/data.txt
Normal file
File diff suppressed because it is too large
Load Diff
25
day18/example.txt
Normal file
25
day18/example.txt
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
5,4
|
||||||
|
4,2
|
||||||
|
4,5
|
||||||
|
3,0
|
||||||
|
2,1
|
||||||
|
6,3
|
||||||
|
2,4
|
||||||
|
1,5
|
||||||
|
0,6
|
||||||
|
3,3
|
||||||
|
2,6
|
||||||
|
5,1
|
||||||
|
1,2
|
||||||
|
5,5
|
||||||
|
2,5
|
||||||
|
6,5
|
||||||
|
1,4
|
||||||
|
0,4
|
||||||
|
6,4
|
||||||
|
1,1
|
||||||
|
6,1
|
||||||
|
1,0
|
||||||
|
0,5
|
||||||
|
1,6
|
||||||
|
2,0
|
3
day18/go.mod
Normal file
3
day18/go.mod
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module stevenlr.com/aoc2024/day18
|
||||||
|
|
||||||
|
go 1.23.3
|
204
day18/main.go
Normal file
204
day18/main.go
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"container/heap"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
positions := readData("example.txt")
|
||||||
|
maze := makeMaze(positions[:12], 7)
|
||||||
|
fmt.Println(part1(maze), 22)
|
||||||
|
|
||||||
|
positions2 := readData("data.txt")
|
||||||
|
maze2 := makeMaze(positions2[:1024], 71)
|
||||||
|
fmt.Println(part1(maze2), 284)
|
||||||
|
|
||||||
|
fmt.Println(6, 1)
|
||||||
|
fmt.Println(part2(positions, 7, 11))
|
||||||
|
|
||||||
|
fmt.Println(part2(positions2, 71, 1023))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Position struct {
|
||||||
|
x, y int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Candidate struct {
|
||||||
|
pos Position
|
||||||
|
estimateCost int
|
||||||
|
}
|
||||||
|
|
||||||
|
type CandidateItem struct {
|
||||||
|
cnd *Candidate
|
||||||
|
index int
|
||||||
|
}
|
||||||
|
|
||||||
|
type CandidateQueue struct {
|
||||||
|
elements []*CandidateItem
|
||||||
|
index map[Position]*CandidateItem
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeCandidateQueue() *CandidateQueue {
|
||||||
|
queue := new(CandidateQueue)
|
||||||
|
queue.index = make(map[Position]*CandidateItem)
|
||||||
|
return queue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (queue *CandidateQueue) Len() int {
|
||||||
|
return len(queue.elements)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (queue *CandidateQueue) Less(i, j int) bool {
|
||||||
|
return queue.elements[i].cnd.estimateCost < queue.elements[j].cnd.estimateCost
|
||||||
|
}
|
||||||
|
|
||||||
|
func (queue *CandidateQueue) Swap(i, j int) {
|
||||||
|
queue.elements[i], queue.elements[j] = queue.elements[j], queue.elements[i]
|
||||||
|
queue.elements[i].index = i
|
||||||
|
queue.elements[j].index = j
|
||||||
|
}
|
||||||
|
|
||||||
|
func (queue *CandidateQueue) Push(x any) {
|
||||||
|
candidate := x.(Candidate)
|
||||||
|
item := &CandidateItem{&candidate, len(queue.elements)}
|
||||||
|
queue.elements = append(queue.elements, item)
|
||||||
|
queue.index[candidate.pos] = item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (queue *CandidateQueue) AddElement(candidate Candidate) {
|
||||||
|
item, alreadyIn := queue.index[candidate.pos]
|
||||||
|
if alreadyIn {
|
||||||
|
item.cnd = &candidate
|
||||||
|
heap.Fix(queue, item.index)
|
||||||
|
} else {
|
||||||
|
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]
|
||||||
|
delete(queue.index, elmt.cnd.pos)
|
||||||
|
return elmt.cnd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeMaze(walls []Position, size int) (maze [][]byte) {
|
||||||
|
maze = make([][]byte, size+2)
|
||||||
|
for y := range maze {
|
||||||
|
maze[y] = make([]byte, size+2)
|
||||||
|
maze[y][0] = '#'
|
||||||
|
maze[y][size+1] = '#'
|
||||||
|
}
|
||||||
|
for x := range size + 2 {
|
||||||
|
maze[0][x] = '#'
|
||||||
|
maze[size+1][x] = '#'
|
||||||
|
}
|
||||||
|
for _, p := range walls {
|
||||||
|
maze[p.y+1][p.x+1] = '#'
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func IntAbs(x int) int {
|
||||||
|
if x < 0 {
|
||||||
|
return -x
|
||||||
|
} else {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func part1(maze [][]byte) (steps int) {
|
||||||
|
startX, startY := 1, 1
|
||||||
|
endX, endY := len(maze)-2, len(maze)-2
|
||||||
|
|
||||||
|
estimateCost := func(x, y int) int {
|
||||||
|
return IntAbs(x-endX) + IntAbs(y-endY)
|
||||||
|
}
|
||||||
|
|
||||||
|
realCost := make([][]int, len(maze))
|
||||||
|
for y := range realCost {
|
||||||
|
realCost[y] = make([]int, len(maze))
|
||||||
|
for x := range realCost[y] {
|
||||||
|
realCost[y][x] = math.MaxInt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
realCost[startY][startX] = 0
|
||||||
|
|
||||||
|
queue := MakeCandidateQueue()
|
||||||
|
queue.AddElement(Candidate{Position{1, 1}, estimateCost(1, 1)})
|
||||||
|
|
||||||
|
for queue.Len() > 0 {
|
||||||
|
candidate := heap.Pop(queue).(*Candidate)
|
||||||
|
|
||||||
|
tryDirection := func(dx, dy int) int {
|
||||||
|
xNext := candidate.pos.x + dx
|
||||||
|
yNext := candidate.pos.y + dy
|
||||||
|
realCostNext := realCost[candidate.pos.y][candidate.pos.x] + 1
|
||||||
|
if xNext == endX && yNext == endY {
|
||||||
|
return realCostNext
|
||||||
|
} else if maze[yNext][xNext] != '#' && realCostNext < realCost[yNext][xNext] {
|
||||||
|
realCost[yNext][xNext] = realCostNext
|
||||||
|
queue.AddElement(Candidate{Position{xNext, yNext}, realCostNext + estimateCost(xNext, yNext)})
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if cost := tryDirection(1, 0); cost >= 0 {
|
||||||
|
return cost
|
||||||
|
}
|
||||||
|
|
||||||
|
if cost := tryDirection(-1, 0); cost >= 0 {
|
||||||
|
return cost
|
||||||
|
}
|
||||||
|
|
||||||
|
if cost := tryDirection(0, 1); cost >= 0 {
|
||||||
|
return cost
|
||||||
|
}
|
||||||
|
|
||||||
|
if cost := tryDirection(0, -1); cost >= 0 {
|
||||||
|
return cost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func part2(walls []Position, size int, okAt int) (blockX, blockY int) {
|
||||||
|
koAt := len(walls)
|
||||||
|
for koAt-okAt > 1 {
|
||||||
|
pivot := (okAt + koAt) / 2
|
||||||
|
maze := makeMaze(walls[:pivot+1], size)
|
||||||
|
result := part1(maze)
|
||||||
|
if result < 0 {
|
||||||
|
koAt = pivot
|
||||||
|
} else {
|
||||||
|
okAt = pivot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return walls[koAt].x, walls[koAt].y
|
||||||
|
}
|
||||||
|
|
||||||
|
func readData(fileName string) (positions []Position) {
|
||||||
|
fp, _ := os.Open(fileName)
|
||||||
|
scanner := bufio.NewScanner(fp)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.Split(strings.TrimSpace(scanner.Text()), ",")
|
||||||
|
x, _ := strconv.Atoi(line[0])
|
||||||
|
y, _ := strconv.Atoi(line[1])
|
||||||
|
positions = append(positions, Position{x, y})
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
Reference in New Issue
Block a user