Finished day 18
This commit is contained in:
parent
5c90e73a5a
commit
cb3b5cdeeb
5 changed files with 3678 additions and 0 deletions
|
|
@ -26,6 +26,7 @@ import (
|
|||
"cpatino.com/advent-of-code/2024/day15"
|
||||
"cpatino.com/advent-of-code/2024/day16"
|
||||
"cpatino.com/advent-of-code/2024/day17"
|
||||
"cpatino.com/advent-of-code/2024/day18"
|
||||
)
|
||||
|
||||
var days = map[int]func(string) (interface{}, interface{}){
|
||||
|
|
@ -46,6 +47,7 @@ var days = map[int]func(string) (interface{}, interface{}){
|
|||
15: day15.Run,
|
||||
16: day16.Run,
|
||||
17: day17.Run,
|
||||
18: day18.Run,
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
|
|
|
|||
163
2024/day18/day18.go
Executable file
163
2024/day18/day18.go
Executable file
|
|
@ -0,0 +1,163 @@
|
|||
package day18
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"container/heap"
|
||||
"fmt"
|
||||
"image"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Direction int
|
||||
|
||||
const (
|
||||
NORTH Direction = iota
|
||||
EAST
|
||||
SOUTH
|
||||
WEST
|
||||
)
|
||||
|
||||
var transformations = map[Direction]image.Point{
|
||||
NORTH: image.Point{X: 0, Y: -1}, EAST: image.Point{X: 1, Y: 0},
|
||||
SOUTH: image.Point{X: 0, Y: 1}, WEST: image.Point{X: -1, Y: 0},
|
||||
}
|
||||
|
||||
type PriorityQueue struct {
|
||||
Items []image.Point
|
||||
Scores map[image.Point]int
|
||||
Heuristics map[image.Point]int
|
||||
}
|
||||
|
||||
func (pq PriorityQueue) Len() int { return len(pq.Items) }
|
||||
func (pq PriorityQueue) Less(i, j int) bool {
|
||||
return pq.Heuristics[pq.Items[i]] < pq.Heuristics[pq.Items[j]]
|
||||
}
|
||||
func (pq PriorityQueue) Swap(i, j int) { pq.Items[i], pq.Items[j] = pq.Items[j], pq.Items[i] }
|
||||
func (pq *PriorityQueue) Push(x interface{}) { pq.Items = append(pq.Items, x.(image.Point)) }
|
||||
func (pq *PriorityQueue) Pop() interface{} {
|
||||
n := len(pq.Items)
|
||||
item := pq.Items[n-1]
|
||||
pq.Items = pq.Items[:n-1]
|
||||
return item
|
||||
}
|
||||
|
||||
func (pq *PriorityQueue) Add(point image.Point) { heap.Push(pq, point) }
|
||||
func (pq *PriorityQueue) Remove() image.Point { return heap.Pop(pq).(image.Point) }
|
||||
|
||||
func manhattanDistance(a, b image.Point) int {
|
||||
return int(math.Abs(float64(a.X-b.X)) + math.Abs(float64(a.Y-b.Y)))
|
||||
}
|
||||
|
||||
func aStar(grid map[image.Point]bool, initial, end image.Point, rows, cols int) map[image.Point]int {
|
||||
bounds := image.Rect(0, 0, cols+1, rows+1)
|
||||
pq := &PriorityQueue{[]image.Point{}, map[image.Point]int{}, map[image.Point]int{}}
|
||||
heap.Init(pq)
|
||||
|
||||
visited := make(map[image.Point]bool)
|
||||
|
||||
pq.Scores[initial] = 0
|
||||
pq.Heuristics[initial] = 0 + manhattanDistance(initial, end)
|
||||
pq.Push(initial)
|
||||
|
||||
for pq.Len() > 0 {
|
||||
current := pq.Remove()
|
||||
|
||||
visited[current] = true
|
||||
|
||||
if current == end {
|
||||
return pq.Scores
|
||||
}
|
||||
|
||||
for _, direction := range []Direction{NORTH, EAST, SOUTH, WEST} {
|
||||
next := current.Add(transformations[direction])
|
||||
if !next.In(bounds) || grid[next] {
|
||||
continue
|
||||
}
|
||||
|
||||
newScore := pq.Scores[current] + 1
|
||||
if existingScore, exists := pq.Scores[next]; !exists || newScore < existingScore {
|
||||
pq.Scores[next] = newScore
|
||||
pq.Heuristics[next] = newScore + manhattanDistance(next, end)
|
||||
heap.Push(pq, next)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pq.Scores
|
||||
}
|
||||
|
||||
func Part1(bytes []image.Point, rows, cols, steps int) int {
|
||||
initial := image.Point{X: 0, Y: 0}
|
||||
target := image.Point{X: cols, Y: rows}
|
||||
|
||||
grid := make(map[image.Point]bool)
|
||||
for _, point := range bytes[:steps] {
|
||||
grid[point] = true
|
||||
}
|
||||
|
||||
results := aStar(grid, initial, target, rows, cols)
|
||||
|
||||
return results[target]
|
||||
}
|
||||
|
||||
func Part2(bytes []image.Point, rows, cols int) string {
|
||||
left, right := 0, len(bytes)-1
|
||||
for left < right {
|
||||
middle := (left + right + 1) / 2
|
||||
if Part1(bytes, rows, cols, middle) != 0 {
|
||||
left = middle
|
||||
} else {
|
||||
right = middle - 1
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%d,%d", bytes[left].X, bytes[left].Y)
|
||||
}
|
||||
|
||||
func Run(filename string) (interface{}, interface{}) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var bytes []image.Point
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if line == "" {
|
||||
scanner.Scan()
|
||||
break
|
||||
}
|
||||
|
||||
parts := strings.Split(line, ",")
|
||||
|
||||
xStr, yStr := parts[0], parts[1]
|
||||
x, _ := strconv.Atoi(xStr)
|
||||
y, _ := strconv.Atoi(yStr)
|
||||
|
||||
bytes = append(bytes, image.Point{X: x, Y: y})
|
||||
}
|
||||
|
||||
var params []int
|
||||
line := scanner.Text()
|
||||
for _, partStr := range strings.Split(line, ",") {
|
||||
part, _ := strconv.Atoi(partStr)
|
||||
params = append(params, part)
|
||||
}
|
||||
|
||||
rows, cols, steps := params[0], params[1], params[2]
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
part1 := Part1(bytes, rows, cols, steps)
|
||||
part2 := Part2(bytes, rows, cols)
|
||||
|
||||
return part1, part2
|
||||
}
|
||||
34
2024/day18/day18_test.go
Executable file
34
2024/day18/day18_test.go
Executable file
|
|
@ -0,0 +1,34 @@
|
|||
package day18
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type TestCase struct {
|
||||
FileName string
|
||||
Part1 interface{}
|
||||
Part2 interface{}
|
||||
}
|
||||
|
||||
func TestDay18(t *testing.T) {
|
||||
tests := []TestCase{
|
||||
{"test.txt", 22, "6,1"},
|
||||
{"input.txt", 252, "5,60"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if _, err := os.Stat(test.FileName); os.IsNotExist(err) {
|
||||
t.Fatalf("test file does not exist: %v", test.FileName)
|
||||
}
|
||||
|
||||
part1, part2 := Run(test.FileName)
|
||||
if test.Part1 != nil && part1 != test.Part1 {
|
||||
t.Fatalf("%s: unexpected part1:\nwant:\t%v\ngot:\t%v", test.FileName, test.Part1, part1)
|
||||
}
|
||||
|
||||
if test.Part2 != nil && part2 != test.Part2 {
|
||||
t.Fatalf("%s: unexpected part2:\nwant:\t%v\ngot:\t%v", test.FileName, test.Part2, part2)
|
||||
}
|
||||
}
|
||||
}
|
||||
3452
2024/day18/input.txt
Executable file
3452
2024/day18/input.txt
Executable file
File diff suppressed because it is too large
Load diff
27
2024/day18/test.txt
Executable file
27
2024/day18/test.txt
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
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
|
||||
|
||||
6,6,12
|
||||
Loading…
Add table
Add a link
Reference in a new issue