2024 day 18 Complete

This commit is contained in:
Brian Buller 2024-12-18 08:00:14 -06:00
parent 5ae4d0ca9b
commit 64bcac7d6a
6 changed files with 3745 additions and 126 deletions

View File

@ -29,8 +29,8 @@ func part2(input []string) {
} }
func solve(m *h.CoordByteMap, minStraight, maxStraight int) int { func solve(m *h.CoordByteMap, minStraight, maxStraight int) int {
q := NewPriorityQueue[heatState](func(a, b heatState) int { q := h.NewHeap[heatState](func(a, b heatState) bool {
return a.heatLoss - b.heatLoss return a.heatLoss < b.heatLoss
}) })
q.Push(heatState{ q.Push(heatState{
state: state{ state: state{
@ -50,7 +50,7 @@ func solve(m *h.CoordByteMap, minStraight, maxStraight int) int {
visitedCoords := make(map[h.Coordinate]bool) visitedCoords := make(map[h.Coordinate]bool)
visited := make(map[string]int) visited := make(map[string]int)
for q.IsNotEmpty() { for q.Len() != 0 {
chk := q.Pop() chk := q.Pop()
if !m.ContainsCoord(*chk.state.pos) { if !m.ContainsCoord(*chk.state.pos) {
continue continue
@ -126,67 +126,6 @@ type heatState struct {
heatLoss int heatLoss int
} }
type PriorityQueue[T comparable] struct {
items []T
comparator func(a, b T) int
}
func NewPriorityQueue[T comparable](comparator func(a, b T) int) PriorityQueue[T] {
return PriorityQueue[T]{comparator: comparator}
}
func (pq *PriorityQueue[T]) Push(item T) {
pq.items = append(pq.items, item)
pq.heapifyUp(len(pq.items) - 1)
}
func (pq *PriorityQueue[T]) Pop() T {
top := pq.items[0]
lastIndex := len(pq.items) - 1
pq.items[0], pq.items[lastIndex] = pq.items[lastIndex], pq.items[0]
pq.items = pq.items[:lastIndex]
pq.heapifyDown(0)
return top
}
func (pq *PriorityQueue[T]) Peek() T {
return pq.items[0]
}
func (pq *PriorityQueue[T]) Len() int {
return len(pq.items)
}
func (pq *PriorityQueue[T]) IsEmpty() bool {
return len(pq.items) == 0
}
func (pq *PriorityQueue[T]) IsNotEmpty() bool {
return !pq.IsEmpty()
}
func (pq *PriorityQueue[T]) heapifyUp(index int) {
for index > 0 {
parentIndex := (index - 1) / 2
if pq.comparator(pq.items[index], pq.items[parentIndex]) < 0 {
pq.items[index], pq.items[parentIndex] = pq.items[parentIndex], pq.items[index]
index = parentIndex
} else {
break
}
}
}
func (pq *PriorityQueue[T]) heapifyDown(index int) {
for {
leftChild, rightChild, smallest := 2*index+1, 2*index+2, index
if leftChild < len(pq.items) && pq.comparator(pq.items[leftChild], pq.items[smallest]) < 0 {
smallest = leftChild
}
if rightChild < len(pq.items) && pq.comparator(pq.items[rightChild], pq.items[smallest]) < 0 {
smallest = rightChild
}
if smallest != index {
pq.items[index], pq.items[smallest] = pq.items[smallest], pq.items[index]
index = smallest
} else {
break
}
}
}
type direction h.Coordinate type direction h.Coordinate
func (d direction) Get(from *h.Coordinate) *h.Coordinate { func (d direction) Get(from *h.Coordinate) *h.Coordinate {
@ -202,6 +141,7 @@ func (d direction) Get(from *h.Coordinate) *h.Coordinate {
} }
return from return from
} }
func (d direction) LeftTurn() direction { func (d direction) LeftTurn() direction {
switch d.String() { switch d.String() {
case "N": case "N":
@ -214,6 +154,7 @@ func (d direction) LeftTurn() direction {
return dirS return dirS
} }
} }
func (d direction) RightTurn() direction { func (d direction) RightTurn() direction {
switch d.String() { switch d.String() {
case "N": case "N":
@ -226,6 +167,7 @@ func (d direction) RightTurn() direction {
return dirN return dirN
} }
} }
func (d direction) String() string { func (d direction) String() string {
switch { switch {
case d.X == dirN.X && d.Y == dirN.Y: case d.X == dirN.X && d.Y == dirN.Y:

3450
2024/day18/input Normal file

File diff suppressed because it is too large Load Diff

113
2024/day18/main.go Normal file
View File

@ -0,0 +1,113 @@
package main
import (
"fmt"
h "git.bullercodeworks.com/brian/adventofcode/helpers"
)
func main() {
inp := h.StdinToStringSlice()
// size, bytes := 6, 12 // Test Input
size, bytes := 70, 1024 // Actual Input
part1(inp, size, bytes)
fmt.Println()
part2(inp, size, bytes)
}
func part1(inp []string, size, bytes int) {
// bytes:= 12 // Test Input
m := h.NewCoordByteMap()
m.BRX, m.BRY = size, size
drops := parseInput(inp)
for i := 0; i < bytes; i++ {
m.Put(drops[i], '#')
}
fmt.Println("# Part 1")
fmt.Println(FindShortestPath(m, func(a, b State) bool {
return a.cnt+a.dist < b.cnt+b.dist
}))
}
func part2(inp []string, size, bytes int) {
m := h.NewCoordByteMap()
m.BRX, m.BRY = size, size
drops := parseInput(inp)
// We can skip up to 'bytes', part1 showed that that many work
for i := 0; i < bytes; i++ {
m.Put(drops[i], '#')
}
fmt.Println("# Part 2")
for tm, b := range drops[bytes+1:] {
m.Put(b, '#')
if FindShortestPath(m, func(a, b State) bool {
return a.dist < b.dist
}) == -1 {
fmt.Printf("Blocked at %d: %s\n", bytes+tm, b.String())
return
}
}
}
func parseInput(inp []string) []h.Coordinate {
var ret []h.Coordinate
for i := range inp {
c := h.Coordinate{}
fmt.Sscanf(inp[i], "%d,%d", &c.X, &c.Y)
ret = append(ret, c)
}
return ret
}
type State struct {
c h.Coordinate
cnt int
dist int
}
func FindShortestPath(inp h.CoordByteMap, less func(a, b State) bool) int {
visited := make(map[h.Coordinate]State)
end := h.Coordinate{X: inp.BRX, Y: inp.BRY}
start := h.Coordinate{X: 0, Y: 0}
startState := State{
c: start,
cnt: 0,
dist: start.Distance(end),
}
queue := h.NewHeap[State](less)
queue.Push(startState)
for queue.Len() > 0 {
step := queue.Pop()
if step.c.Equals(end) {
visited[end] = step
break
}
if _, ok := visited[step.c]; ok {
continue
}
visited[step.c] = step
// Find next steps
for _, n := range step.c.GetOrthNeighbors() {
if !inp.ContainsCoord(n) || inp.Get(n) == '#' {
continue
}
if _, ok := visited[n]; ok {
continue
}
nState := State{
c: n,
cnt: step.cnt + 1,
dist: n.Distance(end),
}
queue.Push(nState)
}
}
if v, ok := visited[end]; ok {
return v.cnt
}
return -1
}

25
2024/day18/testinput Normal file
View 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

151
helpers/heap.go Normal file
View File

@ -0,0 +1,151 @@
package aoc
/* Heap
* This is a heap implementation with slice backing.
* The less function is used to compare elements.
* The heap is a complete binary tree, which means:
* - All levels are filled except possibly for the last level.
* - The last level is filled as much as possible from left to right.
*
* This binary tree also should satisfy the heap property (we use min-heap here):
* - The smallest element is at the root
* - The value of each node is less than or equal to the values of its children
*
* To implement a heap, we use a slice to store the elements with these rules:
* - Root is at 0
* - Left child of node at index i is at 2*i+1
* - Right child of node at index i is at 2*i+2
* - Parent of node at index i is at (i-1)/2
* For example, if we insert: 2, 0, 7, 4, 3, 6, 9
* The heap will look like this:
* i: 0 1 2 3 4 5 6
* [ 0, 2, 6, 4, 3, 7, 9 ]
* And the effective binary tree looks like this:
* 0
* / \
* 2 6
* / \ / \
* 4 3 7 9
* So the root is at index [0],
* the children of [0] art at 2*0+1 = [1] and 2*0+2 = [2]
* The parent of [1] is at (1-1)/2 = [0],
* and the parent of [2] is at (2-1)/2 = [0]
* The children of [1] are at 2*1+1 = [3] and 2*1+2 = [4]
* The parent of [3] is at (3-1)/2 = [1],
* and the parent of [4] is at (4-1)/2 = [1]
* The children of [2] are at 2*2+1 = [5] and 2*2+2 = [6]
* The parent of [5] is at (5-1)/2 = [2],
* and the parent of [6] is at (6-1)/2 = [2]
* Thanks to insomnes (https://github.com/insomnes) for this.
*/
type Heap[T any] struct {
data []T
less func(a, b T) bool
}
func NewHeap[T any](less func(a, b T) bool) *Heap[T] {
return &Heap[T]{data: []T{}, less: less}
}
func (h *Heap[T]) Len() int {
return len(h.data)
}
// Push adds a new element to the heap (the end of the slice)
// Then restores the heap property
func (h *Heap[T]) Push(x T) {
h.data = append(h.data, x)
h.ascend(len(h.data) - 1)
}
// Pop removes the smallest element from the heap (first in the slice)
// Under the hood, it swaps the first and last elements, pops the last element,
// and then restores the heap property by comparing the new psuedo-root with
// children
func (h *Heap[T]) Pop() T {
if len(h.data) == 0 {
panic("heap is empty")
}
h.swap(0, len(h.data)-1)
minVal := h.data[len(h.data)-1]
h.data = h.data[:len(h.data)-1]
h.descend(0)
return minVal
}
// Check the top element without removing it.
func (h *Heap[T]) Peek() T {
if len(h.data) == 0 {
panic("heap is empty")
}
return h.data[0]
}
// Swap child idx with parent idx until the heap property is restored
// For example, if we just added a 2 to 0,4,7:
// [ 0, 4, 7, 2 ]
// Push will call ascend(3) to restore the heap property,
// and first we will check idx parent:
// i = 3 => parent = (3-1)/2 = 1
// 2 < 4 => swap 2 and 4
// New i = 1 (current position of 2, previous pos of 4)
// so the new parent index for 1 is (1-1)/2 = 0
// 2 > 0 => stop (by !h.less(h.data[1], h.data[0]))
func (h *Heap[T]) ascend(i int) {
for {
parent := (i - 1) / 2
if i == 0 || !h.less(h.data[i], h.data[parent]) {
break
}
h.swap(i, parent)
i = parent
}
}
// Swap parent index with the smallest child index until the heap property is restored
// For example, let's say we have original slice: [ 0, 2, 6, 4, 3, 7, 9 ]
// After popping the root, we swap 0 and 9 and remove 0 (at index 6):
// [ 9, 2, 6, 4, 3, 7 ]
// Pop will call descend(0) to restore the heap property,
// and first we will check index parent:
// i = 0 => left = 2*0+1 = 1, right = 2*0+2 = 2
// Values lv = 2, rv = 6, rv = 9
// 2 < 9 => smallest should be set to left index:
// smallest = 1, left = 1, right = 2
// 6 > 2, do not change smallest
// smallest index (1) != index (0), so we swap 0 and 1 and set index to 1
// [ 2, 9, 6, 4, 3, 7 ]
// i = 1 => left = 2*1+1 = 3, right = 2*1+2 = 4
// Values lv = 4, rv = 3, sv = 9
// 4 < 9 => smallest should be set to left index:
// smallest = 3, left = 3, right = 4
// 3 < 4 => smallest should be set to right index:
// smallest = 4, left = 3, right = 4
// smallest index (4) != index (1), so we swap 1 and 4 and set index to 4
// [ 2, 3, 6, 4, 9, 7 ]
// i = 4 => left = 2*4+1 = 9, right = 2*4+2 = 10
// meaning there are no children, check that smallest index (4) == index (4) and stop
func (h *Heap[T]) descend(i int) {
size := len(h.data)
for {
leftI, rightI := 2*i+1, 2*i+2
smallestI := i
if leftI < size && h.less(h.data[leftI], h.data[smallestI]) {
smallestI = leftI
}
if rightI < size && h.less(h.data[rightI], h.data[smallestI]) {
smallestI = rightI
}
if smallestI == i {
break
}
h.swap(i, smallestI)
i = smallestI
}
}
func (h *Heap[T]) swap(i, j int) {
h.data[i], h.data[j] = h.data[j], h.data[i]
}

View File

@ -1,62 +0,0 @@
package aoc
type PriorityQueue[T comparable] struct {
items []T
comparator func(a, b T) int
}
func NewPriorityQueue[T comparable](comparator func(a, b T) int) PriorityQueue[T] {
return PriorityQueue[T]{comparator: comparator}
}
func (pq *PriorityQueue[T]) Push(item T) {
pq.items = append(pq.items, item)
pq.heapifyUp(len(pq.items) - 1)
}
func (pq *PriorityQueue[T]) Pop() T {
top := pq.items[0]
lastIndex := len(pq.items) - 1
pq.items[0], pq.items[lastIndex] = pq.items[lastIndex], pq.items[0]
pq.items = pq.items[:lastIndex]
pq.heapifyDown(0)
return top
}
func (pq *PriorityQueue[T]) Peek() T {
return pq.items[0]
}
func (pq *PriorityQueue[T]) Len() int {
return len(pq.items)
}
func (pq *PriorityQueue[T]) IsEmpty() bool {
return len(pq.items) == 0
}
func (pq *PriorityQueue[T]) IsNotEmpty() bool {
return !pq.IsEmpty()
}
func (pq *PriorityQueue[T]) heapifyUp(index int) {
for index > 0 {
parentIndex := (index - 1) / 2
if pq.comparator(pq.items[index], pq.items[parentIndex]) < 0 {
pq.items[index], pq.items[parentIndex] = pq.items[parentIndex], pq.items[index]
index = parentIndex
} else {
break
}
}
}
func (pq *PriorityQueue[T]) heapifyDown(index int) {
for {
leftChild, rightChild, smallest := 2*index+1, 2*index+2, index
if leftChild < len(pq.items) && pq.comparator(pq.items[leftChild], pq.items[smallest]) < 0 {
smallest = leftChild
}
if rightChild < len(pq.items) && pq.comparator(pq.items[rightChild], pq.items[smallest]) < 0 {
smallest = rightChild
}
if smallest != index {
pq.items[index], pq.items[smallest] = pq.items[smallest], pq.items[index]
index = smallest
} else {
break
}
}
}