2024 day 18 Complete
This commit is contained in:
parent
5ae4d0ca9b
commit
64bcac7d6a
@ -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
3450
2024/day18/input
Normal file
File diff suppressed because it is too large
Load Diff
113
2024/day18/main.go
Normal file
113
2024/day18/main.go
Normal 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
25
2024/day18/testinput
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
|
151
helpers/heap.go
Normal file
151
helpers/heap.go
Normal 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]
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user