Finished day 18

This commit is contained in:
Ceferino Patino 2024-12-17 23:58:40 -06:00
commit cb3b5cdeeb
Signed by: c4patino
SSH key fingerprint: SHA256:Wu+cU1t+7zVT9wzew/4meNmeulo66NzMqc22MdDBgXI
5 changed files with 3678 additions and 0 deletions

View file

@ -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
View 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
View 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

File diff suppressed because it is too large Load diff

27
2024/day18/test.txt Executable file
View 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