205 lines
4.2 KiB
Go
205 lines
4.2 KiB
Go
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
|
|
}
|